reference/FIELD-TYPE-GUARDS.md
reference/FIELD-TYPE-GUARDS.mdBrowse 12 files
10,900 bytes
Token encoding: o200k_base
Snapshot 9a2cdcb
Payload Field Type Guards Reference
Complete reference with detailed examples and patterns. See FIELDS.md for quick reference table of all guards.
Structural Guards
fieldHasSubFields
Checks if field contains nested fields (group, array, row, or collapsible).
import type { Field } from 'payload'
import { fieldHasSubFields } from 'payload'
function traverseFields(fields: Field[]): void {
fields.forEach((field) => {
if (fieldHasSubFields(field)) {
// Safe to access field.fields
traverseFields(field.fields)
}
})
}
Signature:
fieldHasSubFields<TField extends ClientField | Field>(
field: TField
): field is TField & (FieldWithSubFieldsClient | FieldWithSubFields)
Common Pattern - Exclude Arrays:
if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
// Groups, rows, collapsibles only (not arrays)
}
fieldIsArrayType
Checks if field type is 'array'.
import { fieldIsArrayType } from 'payload'
if (fieldIsArrayType(field)) {
// field.type === 'array'
console.log(`Min rows: ${field.minRows}`)
console.log(`Max rows: ${field.maxRows}`)
}
Signature:
fieldIsArrayType<TField extends ClientField | Field>(
field: TField
): field is TField & (ArrayFieldClient | ArrayField)
fieldIsBlockType
Checks if field type is 'blocks'.
import { fieldIsBlockType } from 'payload'
if (fieldIsBlockType(field)) {
// field.type === 'blocks'
field.blocks.forEach((block) => {
console.log(`Block: ${block.slug}`)
})
}
Signature:
fieldIsBlockType<TField extends ClientField | Field>(
field: TField
): field is TField & (BlocksFieldClient | BlocksField)
Common Pattern - Distinguish Containers:
if (fieldIsArrayType(field)) {
// Handle array rows
} else if (fieldIsBlockType(field)) {
// Handle block types
}
fieldIsGroupType
Checks if field type is 'group'.
import { fieldIsGroupType } from 'payload'
if (fieldIsGroupType(field)) {
// field.type === 'group'
console.log(`Interface: ${field.interfaceName}`)
}
Signature:
fieldIsGroupType<TField extends ClientField | Field>(
field: TField
): field is TField & (GroupFieldClient | GroupField)
Capability Guards
fieldSupportsMany
Checks if field can have multiple values (select, relationship, or upload with hasMany).
import { fieldSupportsMany } from 'payload'
if (fieldSupportsMany(field)) {
// field.type is 'select' | 'relationship' | 'upload'
// Safe to check field.hasMany
if (field.hasMany) {
console.log('Field accepts multiple values')
}
}
Signature:
fieldSupportsMany<TField extends ClientField | Field>(
field: TField
): field is TField & (FieldWithManyClient | FieldWithMany)
fieldHasMaxDepth
Checks if field is relationship/upload/join with numeric maxDepth property.
import { fieldHasMaxDepth } from 'payload'
if (fieldHasMaxDepth(field)) {
// field.type is 'upload' | 'relationship' | 'join'
// AND field.maxDepth is number
const remainingDepth = field.maxDepth - currentDepth
}
Signature:
fieldHasMaxDepth<TField extends ClientField | Field>(
field: TField
): field is TField & (FieldWithMaxDepthClient | FieldWithMaxDepth)
fieldShouldBeLocalized
Checks if field needs localization handling (accounts for parent localization).
import { fieldShouldBeLocalized } from 'payload'
function processField(field: Field, parentIsLocalized: boolean) {
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
// Create locale-specific table or index
}
}
Signature:
fieldShouldBeLocalized({
field,
parentIsLocalized,
}: {
field: ClientField | ClientTab | Field | Tab
parentIsLocalized: boolean
}): boolean
// Accounts for parent localization
if (fieldShouldBeLocalized({ field, parentIsLocalized: false })) {
/* ... */
}
fieldIsVirtual
Checks if field is virtual (computed or virtual relationship).
import { fieldIsVirtual } from 'payload'
if (fieldIsVirtual(field)) {
// field.virtual is truthy
if (typeof field.virtual === 'string') {
// Virtual relationship path
console.log(`Virtual path: ${field.virtual}`)
} else {
// Computed virtual field (uses hooks)
}
}
Signature:
fieldIsVirtual(field: Field | Tab): boolean
Data Guards
fieldAffectsData
Most commonly used guard. Checks if field stores data (has name and is not UI-only).
import { fieldAffectsData } from 'payload'
function generateSchema(fields: Field[]) {
fields.forEach((field) => {
if (fieldAffectsData(field)) {
// Safe to access field.name
schema[field.name] = getFieldType(field)
}
})
}
Signature:
fieldAffectsData<TField extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: TField
): field is TField & (FieldAffectingDataClient | FieldAffectingData)
Pattern - Data Fields Only:
const dataFields = fields.filter(fieldAffectsData)
fieldIsPresentationalOnly
Checks if field is UI-only (type 'ui').
import { fieldIsPresentationalOnly } from 'payload'
if (fieldIsPresentationalOnly(field)) {
// field.type === 'ui'
// Skip in data operations, GraphQL schema, etc.
return
}
Signature:
fieldIsPresentationalOnly<TField extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: TField
): field is TField & (UIFieldClient | UIField)
fieldIsID
Checks if field name is exactly 'id'.
import { fieldIsID } from 'payload'
if (fieldIsID(field)) {
// field.name === 'id'
// Special handling for ID field
}
Signature:
fieldIsID<TField extends ClientField | Field>(
field: TField
): field is { name: 'id' } & TField
fieldIsHiddenOrDisabled
Checks if field is hidden or admin-disabled.
import { fieldIsHiddenOrDisabled } from 'payload'
const visibleFields = fields.filter((field) => !fieldIsHiddenOrDisabled(field))
Signature:
fieldIsHiddenOrDisabled<TField extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: TField
): field is { admin: { hidden: true } } & TField
Layout Guards
fieldIsSidebar
Checks if field is positioned in sidebar.
import { fieldIsSidebar } from 'payload'
const [mainFields, sidebarFields] = fields.reduce(
([main, sidebar], field) => {
if (fieldIsSidebar(field)) {
return [main, [...sidebar, field]]
}
return [[...main, field], sidebar]
},
[[], []],
)
Signature:
fieldIsSidebar<TField extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: TField
): field is { admin: { position: 'sidebar' } } & TField
Tab & Group Guards
tabHasName
Checks if tab is named (stores data under tab name).
import { tabHasName } from 'payload'
tabs.forEach((tab) => {
if (tabHasName(tab)) {
// tab.name exists
dataPath.push(tab.name)
}
// Process tab.fields
})
Signature:
tabHasName<TField extends ClientTab | Tab>(
tab: TField
): tab is NamedTab & TField
groupHasName
Checks if group is named (stores data under group name).
import { groupHasName } from 'payload'
if (groupHasName(group)) {
// group.name exists
return data[group.name]
}
Signature:
groupHasName(group: Partial<NamedGroupFieldClient>): group is NamedGroupFieldClient
Option & Value Guards
optionIsObject
Checks if option is object format {label, value} vs string.
import { optionIsObject } from 'payload'
field.options.forEach((option) => {
if (optionIsObject(option)) {
console.log(`${option.label}: ${option.value}`)
} else {
console.log(option) // string value
}
})
Signature:
optionIsObject(option: Option): option is OptionObject
optionsAreObjects
Checks if entire options array contains objects.
import { optionsAreObjects } from 'payload'
if (optionsAreObjects(field.options)) {
// All options are OptionObject[]
const labels = field.options.map((opt) => opt.label)
}
Signature:
optionsAreObjects(options: Option[]): options is OptionObject[]
optionIsValue
Checks if option is string value (not object).
import { optionIsValue } from 'payload'
if (optionIsValue(option)) {
// option is string
const value = option
}
Signature:
optionIsValue(option: Option): option is string
valueIsValueWithRelation
Checks if relationship value is polymorphic format {relationTo, value}.
import { valueIsValueWithRelation } from 'payload'
if (valueIsValueWithRelation(fieldValue)) {
// fieldValue.relationTo exists
// fieldValue.value exists
console.log(`Related to ${fieldValue.relationTo}: ${fieldValue.value}`)
}
Signature:
valueIsValueWithRelation(value: unknown): value is ValueWithRelation
Common Patterns
Recursive Field Traversal
import { fieldAffectsData, fieldHasSubFields } from 'payload'
function traverseFields(fields: Field[], callback: (field: Field) => void) {
fields.forEach((field) => {
if (fieldAffectsData(field)) {
callback(field)
}
if (fieldHasSubFields(field)) {
traverseFields(field.fields, callback)
}
})
}
Filter Data-Bearing Fields
import { fieldAffectsData, fieldIsPresentationalOnly, fieldIsHiddenOrDisabled } from 'payload'
const dataFields = fields.filter(
(field) =>
fieldAffectsData(field) && !fieldIsPresentationalOnly(field) && !fieldIsHiddenOrDisabled(field),
)
Container Type Switching
import { fieldIsArrayType, fieldIsBlockType, fieldHasSubFields } from 'payload'
if (fieldIsArrayType(field)) {
// Handle array-specific logic
} else if (fieldIsBlockType(field)) {
// Handle blocks-specific logic
} else if (fieldHasSubFields(field)) {
// Handle group/row/collapsible
}
Safe Property Access
import { fieldSupportsMany, fieldHasMaxDepth } from 'payload'
// Without guard - TypeScript error
// if (field.hasMany) { /* ... */ }
// With guard - safe access
if (fieldSupportsMany(field) && field.hasMany) {
console.log('Multiple values supported')
}
if (fieldHasMaxDepth(field)) {
const depth = field.maxDepth // TypeScript knows this is number
}
Type Preservation
All guards preserve the original type constraint:
import type { ClientField, Field } from 'payload'
import { fieldHasSubFields } from 'payload'
function processServerField(field: Field) {
if (fieldHasSubFields(field)) {
// field is Field & FieldWithSubFields (not ClientField)
}
}
function processClientField(field: ClientField) {
if (fieldHasSubFields(field)) {
// field is ClientField & FieldWithSubFieldsClient
}
}Referenced from SKILL.md
Source excerpt starting at line 40.SKILL.mdView in source ↗40| Plugin hooks | Preserve existing hooks in array | [PLUGIN-DEVELOPMENT.md#adding-hooks](reference/PLUGIN-DEVELOPMENT.md#adding-hooks) |41| Check field type | Type guard functions | [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) |
Source excerpt starting at line 508.508- **[FIELDS.md](reference/FIELDS.md)** - All field types, validation, admin options509- **[FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md)** - Type guards for runtime field type checking and narrowing510- **[COLLECTIONS.md](reference/COLLECTIONS.md)** - Collection configs, auth, upload, drafts, live preview