title: Field Type Guards description: Runtime field type checking and safe type narrowing
Type guards for runtime field type checking and safe type narrowing.
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)
}
})
}
// Filter data fields
const dataFields = fields.filter(fieldAffectsData)
Checks if field contains nested fields (group, array, row, or collapsible).
import { fieldHasSubFields } from 'payload'
function traverseFields(fields: Field[]): void {
fields.forEach((field) => {
if (fieldHasSubFields(field)) {
// Safe to access field.fields
traverseFields(field.fields)
}
})
}
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}`)
}
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'
if (field.hasMany) {
console.log('Field accepts multiple values')
}
}
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
}
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') {
console.log(`Virtual path: ${field.virtual}`)
}
}
import { fieldIsBlockType } from 'payload'
if (fieldIsBlockType(field)) {
// field.type === 'blocks'
field.blocks.forEach((block) => {
console.log(`Block: ${block.slug}`)
})
}
import { fieldIsGroupType } from 'payload'
if (fieldIsGroupType(field)) {
// field.type === 'group'
console.log(`Interface: ${field.interfaceName}`)
}
import { fieldIsPresentationalOnly } from 'payload'
if (fieldIsPresentationalOnly(field)) {
// field.type === 'ui'
// Skip in data operations, GraphQL schema, etc.
return
}
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)
}
})
}
import { fieldAffectsData, fieldIsPresentationalOnly, fieldIsHiddenOrDisabled } from 'payload'
const dataFields = fields.filter(
(field) =>
fieldAffectsData(field) && !fieldIsPresentationalOnly(field) && !fieldIsHiddenOrDisabled(field),
)
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
}
import { fieldSupportsMany, fieldHasMaxDepth } from 'payload'
// 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 Guard | Checks For | Use When |
|---|---|---|
fieldAffectsData |
Field stores data (has name) | Need to access field data or name |
fieldHasSubFields |
Field contains nested fields | Recursively traverse fields |
fieldIsArrayType |
Field is array type | Distinguish arrays from other containers |
fieldIsBlockType |
Field is blocks type | Handle blocks-specific logic |
fieldIsGroupType |
Field is group type | Handle group-specific logic |
fieldSupportsMany |
Field can have multiple values | Check for hasMany support |
fieldHasMaxDepth |
Field supports depth control | Control relationship/upload/join depth |
fieldIsPresentationalOnly |
Field is UI-only | Exclude from data operations |
fieldIsSidebar |
Field positioned in sidebar | Separate sidebar rendering |
fieldIsID |
Field name is 'id' | Special ID field handling |
fieldIsHiddenOrDisabled |
Field is hidden or disabled | Filter from UI operations |
fieldShouldBeLocalized |
Field needs localization | Proper locale table checks |
fieldIsVirtual |
Field is virtual | Skip in database transforms |
tabHasName |
Tab is named (stores data) | Distinguish named vs unnamed tabs |
groupHasName |
Group is named (stores data) | Distinguish named vs unnamed groups |
optionIsObject |
Option is {label, value} |
Access option properties safely |
optionsAreObjects |
All options are objects | Batch option processing |
optionIsValue |
Option is string value | Handle string options |
valueIsValueWithRelation |
Value is polymorphic relationship | Handle polymorphic relationships |