field-type-guards.md 6.6 KB


title: Field Type Guards description: Runtime field type checking and safe type narrowing

tags: [payload, typescript, type-guards, fields]

Payload Field Type Guards

Type guards for runtime field type checking and safe type narrowing.

Most Common 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)
    }
  })
}

// Filter data fields
const dataFields = fields.filter(fieldAffectsData)

fieldHasSubFields

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)
    }
  })
}

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}`)
}

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'
  if (field.hasMany) {
    console.log('Field accepts multiple values')
  }
}

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
}

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') {
    console.log(`Virtual path: ${field.virtual}`)
  }
}

Type Checking Guards

fieldIsBlockType

import { fieldIsBlockType } from 'payload'

if (fieldIsBlockType(field)) {
  // field.type === 'blocks'
  field.blocks.forEach((block) => {
    console.log(`Block: ${block.slug}`)
  })
}

fieldIsGroupType

import { fieldIsGroupType } from 'payload'

if (fieldIsGroupType(field)) {
  // field.type === 'group'
  console.log(`Interface: ${field.interfaceName}`)
}

fieldIsPresentationalOnly

import { fieldIsPresentationalOnly } from 'payload'

if (fieldIsPresentationalOnly(field)) {
  // field.type === 'ui'
  // Skip in data operations, GraphQL schema, etc.
  return
}

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'

// 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
}

All Available Guards

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