# Payload Field Types Reference

Complete reference for all Payload field types with examples.

## Text Field

```ts
import type { TextField } from 'payload'

const textField: TextField = {
  name: 'title',
  type: 'text',
  required: true,
  unique: true,
  minLength: 5,
  maxLength: 100,
  index: true,
  localized: true,
  defaultValue: 'Default Title',
  validate: (value) => Boolean(value) || 'Required',
  admin: {
    placeholder: 'Enter title...',
    position: 'sidebar',
    condition: (data) => data.showTitle === true,
  },
}
```

> **When to use `position: 'sidebar'`:** Reserve the sidebar for short fields that
> give quick insight into the content — status, category, author, publish date,
> slug. Avoid it for fields that need horizontal space to be useful, like a
> description, rich text content, or long text — those belong in the main document
> area. (`title` above is shown in the sidebar only to demonstrate the option.)

### Slug Field Helper

Built-in helper for auto-generating slugs. **Use this for all slugs** instead of
hand-rolling a `{ name: 'slug', type: 'text', unique: true }` field — it
auto-generates the slug from the title, adds a regenerate toggle, and handles
uniqueness and indexing. Call it with no args when the collection has a `title`
field — the slug is generated from `title` by default:

```ts
import { slugField } from 'payload'
import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    { name: 'title', type: 'text', required: true },
    slugField(), // name: 'slug', useAsSlug: 'title', required, unique, position: 'sidebar'
  ],
}
```

`useAsSlug` defaults to `'title'`, so if the collection has **no `title` field**,
you must pass the source field explicitly — otherwise the slug generates from a
field that doesn't exist:

```ts
// Collection keyed on `name` instead of `title`
fields: [{ name: 'name', type: 'text', required: true }, slugField({ useAsSlug: 'name' })]
```

Override defaults when needed (`overrides` receives the generated `RowField`):

```ts
slugField({
  name: 'slug', // defaults to 'slug'
  useAsSlug: 'title', // defaults to 'title'
  checkboxName: 'generateSlug', // defaults to 'generateSlug'
  localized: true,
  required: true, // defaults to true
  position: 'sidebar', // default; the slug is a short field well-suited to the sidebar
  overrides: (field) => {
    field.fields[1].label = 'Custom Slug Label'
    return field
  },
})
```

## Rich Text (Lexical)

```ts
import type { RichTextField } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { HeadingFeature, LinkFeature } from '@payloadcms/richtext-lexical'

const richTextField: RichTextField = {
  name: 'content',
  type: 'richText',
  required: true,
  localized: true,
  editor: lexicalEditor({
    features: ({ defaultFeatures }) => [
      ...defaultFeatures,
      HeadingFeature({
        enabledHeadingSizes: ['h1', 'h2', 'h3'],
      }),
      LinkFeature({
        enabledCollections: ['posts', 'pages'],
      }),
    ],
  }),
}
```

### Advanced Lexical Configuration

```ts
import {
  BoldFeature,
  EXPERIMENTAL_TableFeature,
  FixedToolbarFeature,
  HeadingFeature,
  IndentFeature,
  InlineToolbarFeature,
  ItalicFeature,
  LinkFeature,
  OrderedListFeature,
  UnderlineFeature,
  UnorderedListFeature,
  lexicalEditor,
} from '@payloadcms/richtext-lexical'

// Global editor config with full features
export default buildConfig({
  editor: lexicalEditor({
    features: () => {
      return [
        UnderlineFeature(),
        BoldFeature(),
        ItalicFeature(),
        OrderedListFeature(),
        UnorderedListFeature(),
        LinkFeature({
          enabledCollections: ['pages'],
          fields: ({ defaultFields }) => {
            const defaultFieldsWithoutUrl = defaultFields.filter((field) => {
              if ('name' in field && field.name === 'url') return false
              return true
            })

            return [
              ...defaultFieldsWithoutUrl,
              {
                name: 'url',
                type: 'text',
                admin: {
                  condition: ({ linkType }) => linkType !== 'internal',
                },
                label: ({ t }) => t('fields:enterURL'),
                required: true,
              },
            ]
          },
        }),
        IndentFeature(),
        EXPERIMENTAL_TableFeature(),
      ]
    },
  }),
})

// Field-specific editor with custom toolbar
const richTextWithToolbars: RichTextField = {
  name: 'richText',
  type: 'richText',
  editor: lexicalEditor({
    features: ({ rootFeatures }) => {
      return [
        ...rootFeatures,
        HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
        FixedToolbarFeature(),
        InlineToolbarFeature(),
      ]
    },
  }),
  label: false,
}
```

## Relationship

```ts
import type { RelationshipField } from 'payload'

// Single relationship
const singleRelationship: RelationshipField = {
  name: 'author',
  type: 'relationship',
  relationTo: 'users',
  required: true,
  maxDepth: 2,
}

// Multiple relationships (hasMany)
const multipleRelationship: RelationshipField = {
  name: 'categories',
  type: 'relationship',
  relationTo: 'categories',
  hasMany: true,
  filterOptions: {
    active: { equals: true },
  },
}

// Polymorphic relationship
const polymorphicRelationship: PolymorphicRelationshipField = {
  name: 'relatedContent',
  type: 'relationship',
  relationTo: ['posts', 'pages'],
  hasMany: true,
}
```

## Array

```ts
import type { ArrayField } from 'payload'

const arrayField: ArrayField = {
  name: 'slides',
  type: 'array',
  minRows: 2,
  maxRows: 10,
  labels: {
    singular: 'Slide',
    plural: 'Slides',
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'image',
      type: 'upload',
      relationTo: 'media',
    },
  ],
  admin: {
    initCollapsed: true,
  },
}
```

## Blocks

```ts
import type { BlocksField, Block } from 'payload'

const HeroBlock: Block = {
  slug: 'hero',
  interfaceName: 'HeroBlock',
  fields: [
    {
      name: 'heading',
      type: 'text',
      required: true,
    },
    {
      name: 'background',
      type: 'upload',
      relationTo: 'media',
    },
  ],
}

const ContentBlock: Block = {
  slug: 'content',
  fields: [
    {
      name: 'text',
      type: 'richText',
    },
  ],
}

const blocksField: BlocksField = {
  name: 'layout',
  type: 'blocks',
  blocks: [HeroBlock, ContentBlock],
}
```

## Select

```ts
import type { SelectField } from 'payload'

// Use select for genuine taxonomy. For publish state, enable versions.drafts
// and rely on the auto-injected _status field instead of a custom select.
const selectField: SelectField = {
  name: 'priority',
  type: 'select',
  options: [
    { label: 'Low', value: 'low' },
    { label: 'Medium', value: 'medium' },
    { label: 'High', value: 'high' },
  ],
  defaultValue: 'medium',
  required: true,
}

// Multiple select
const multiSelectField: SelectField = {
  name: 'tags',
  type: 'select',
  hasMany: true,
  options: ['tech', 'news', 'sports'],
}
```

## Upload

```ts
import type { UploadField } from 'payload'

const uploadField: UploadField = {
  name: 'featuredImage',
  type: 'upload',
  relationTo: 'media',
  required: true,
  filterOptions: {
    mimeType: { contains: 'image' },
  },
}
```

## Point (Geolocation)

Point fields store geographic coordinates with automatic 2dsphere indexing for geospatial queries.

```ts
import type { PointField } from 'payload'

const locationField: PointField = {
  name: 'location',
  type: 'point',
  label: 'Location',
  required: true,
}

// Returns [longitude, latitude]
// Example: [-122.4194, 37.7749] for San Francisco
```

### Geospatial Queries

```ts
// Query by distance (sorted by nearest first)
const nearbyLocations = await payload.find({
  collection: 'stores',
  where: {
    location: {
      near: [10, 20], // [longitude, latitude]
      maxDistance: 5000, // in meters
      minDistance: 1000,
    },
  },
})

// Query within polygon area
const polygon: Point[] = [
  [9.0, 19.0], // bottom-left
  [9.0, 21.0], // top-left
  [11.0, 21.0], // top-right
  [11.0, 19.0], // bottom-right
  [9.0, 19.0], // closing point
]

const withinArea = await payload.find({
  collection: 'stores',
  where: {
    location: {
      within: {
        type: 'Polygon',
        coordinates: [polygon],
      },
    },
  },
})

// Query intersecting area
const intersecting = await payload.find({
  collection: 'stores',
  where: {
    location: {
      intersects: {
        type: 'Polygon',
        coordinates: [polygon],
      },
    },
  },
})
```

**Note**: Point fields are not supported in SQLite.

## Join Fields

Join fields create reverse relationships, allowing you to access related documents from the "other side" of a relationship.

```ts
import type { JoinField } from 'payload'

// From Users collection - show user's orders
const ordersJoinField: JoinField = {
  name: 'orders',
  type: 'join',
  collection: 'orders',
  on: 'customer', // The field in 'orders' that references this user
  admin: {
    allowCreate: false,
    defaultColumns: ['id', 'createdAt', 'total', 'currency', 'items'],
  },
}

// From Users collection - show user's cart
const cartJoinField: JoinField = {
  name: 'cart',
  type: 'join',
  collection: 'carts',
  on: 'customer',
  admin: {
    allowCreate: false,
    defaultColumns: ['id', 'createdAt', 'total', 'currency'],
  },
}
```

## Virtual Fields

```ts
import type { TextField } from 'payload'

// Computed from siblings
const computedVirtualField: TextField = {
  name: 'fullName',
  type: 'text',
  virtual: true,
  hooks: {
    afterRead: [({ siblingData }) => `${siblingData.firstName} ${siblingData.lastName}`],
  },
}

// From relationship path
const pathVirtualField: TextField = {
  name: 'authorName',
  type: 'text',
  virtual: 'author.name',
}
```

## Conditional Fields

```ts
import type { UploadField, CheckboxField } from 'payload'

// Simple boolean condition
const enableFeatureField: CheckboxField = {
  name: 'enableFeature',
  type: 'checkbox',
}

const conditionalField: TextField = {
  name: 'featureText',
  type: 'text',
  admin: {
    condition: (data) => data.enableFeature === true,
  },
}

// Sibling data condition (from hero field pattern)
const typeField: SelectField = {
  name: 'type',
  type: 'select',
  options: ['none', 'highImpact', 'mediumImpact', 'lowImpact'],
  defaultValue: 'lowImpact',
}

const mediaField: UploadField = {
  name: 'media',
  type: 'upload',
  relationTo: 'media',
  admin: {
    condition: (_, { type } = {}) => ['highImpact', 'mediumImpact'].includes(type),
  },
  required: true,
}
```

## Radio

Radio fields present options as radio buttons for single selection.

```ts
import type { RadioField } from 'payload'

const radioField: RadioField = {
  name: 'priority',
  type: 'radio',
  options: [
    { label: 'Low', value: 'low' },
    { label: 'Medium', value: 'medium' },
    { label: 'High', value: 'high' },
  ],
  defaultValue: 'medium',
  admin: {
    layout: 'horizontal', // or 'vertical'
  },
}
```

## Row (Layout)

Row fields arrange fields horizontally in the admin panel (presentational only).

```ts
import type { RowField } from 'payload'

const rowField: RowField = {
  type: 'row',
  fields: [
    {
      name: 'firstName',
      type: 'text',
      admin: { width: '50%' },
    },
    {
      name: 'lastName',
      type: 'text',
      admin: { width: '50%' },
    },
  ],
}
```

## Collapsible (Layout)

Collapsible fields group fields in an expandable/collapsible section.

```ts
import type { CollapsibleField } from 'payload'

const collapsibleField: CollapsibleField = {
  label: ({ data }) => data?.title || 'Advanced Options',
  type: 'collapsible',
  admin: {
    initCollapsed: true,
  },
  fields: [
    { name: 'customCSS', type: 'textarea' },
    { name: 'customJS', type: 'code' },
  ],
}
```

## UI (Custom Components)

UI fields allow fully custom React components in the admin (no data stored).

```ts
import type { UIField } from 'payload'

const uiField: UIField = {
  name: 'customMessage',
  type: 'ui',
  admin: {
    components: {
      Field: '/path/to/CustomFieldComponent',
      Cell: '/path/to/CustomCellComponent', // For list view
    },
  },
}
```

## Tabs & Groups

```ts
import type { TabsField, GroupField } from 'payload'

// Tabs
const tabsField: TabsField = {
  type: 'tabs',
  tabs: [
    {
      label: 'Content',
      fields: [
        { name: 'title', type: 'text' },
        { name: 'body', type: 'richText' },
      ],
    },
    {
      label: 'SEO',
      fields: [
        { name: 'metaTitle', type: 'text' },
        { name: 'metaDescription', type: 'textarea' },
      ],
    },
  ],
}

// Group (named)
const groupField: GroupField = {
  name: 'meta',
  type: 'group',
  fields: [
    { name: 'title', type: 'text' },
    { name: 'description', type: 'textarea' },
  ],
}
```

## Reusable Field Factories

Create composable field patterns that can be customized with overrides.

```ts
import type { Field, GroupField } from 'payload'

// Utility for deep merging
const deepMerge = <T>(target: T, source: Partial<T>): T => {
  // Implementation would deeply merge objects
  return { ...target, ...source }
}

// Reusable link field factory
type LinkType = (options?: {
  appearances?: ('default' | 'outline')[] | false
  disableLabel?: boolean
  overrides?: Record<string, unknown>
}) => GroupField

export const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } = {}) => {
  const linkField: GroupField = {
    name: 'link',
    type: 'group',
    admin: {
      hideGutter: true,
    },
    fields: [
      {
        type: 'row',
        fields: [
          {
            name: 'type',
            type: 'radio',
            options: [
              { label: 'Internal link', value: 'reference' },
              { label: 'Custom URL', value: 'custom' },
            ],
            defaultValue: 'reference',
            admin: {
              layout: 'horizontal',
              width: '50%',
            },
          },
          {
            name: 'newTab',
            type: 'checkbox',
            label: 'Open in new tab',
            admin: {
              width: '50%',
              style: {
                alignSelf: 'flex-end',
              },
            },
          },
        ],
      },
      {
        name: 'reference',
        type: 'relationship',
        relationTo: ['pages'],
        required: true,
        maxDepth: 1,
        admin: {
          condition: (_, siblingData) => siblingData?.type === 'reference',
        },
      },
      {
        name: 'url',
        type: 'text',
        label: 'Custom URL',
        required: true,
        admin: {
          condition: (_, siblingData) => siblingData?.type === 'custom',
        },
      },
    ],
  }

  if (!disableLabel) {
    linkField.fields.push({
      name: 'label',
      type: 'text',
      required: true,
    })
  }

  if (appearances !== false) {
    linkField.fields.push({
      name: 'appearance',
      type: 'select',
      defaultValue: 'default',
      options: [
        { label: 'Default', value: 'default' },
        { label: 'Outline', value: 'outline' },
      ],
    })
  }

  return deepMerge(linkField, overrides) as GroupField
}

// Usage
const navItem = link({ appearances: false })
const ctaButton = link({
  overrides: {
    name: 'cta',
    admin: {
      description: 'Call to action button',
    },
  },
})
```

## Field Type Guards

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

| Type Guard                  | Checks For                                                  | Use When                                 |
| --------------------------- | ----------------------------------------------------------- | ---------------------------------------- |
| `fieldAffectsData`          | Field stores data (has name, not UI-only)                   | Need to access field data or name        |
| `fieldHasSubFields`         | Field contains nested fields (group/array/row/collapsible)  | Need to 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 (select/relationship/upload) | Check for `hasMany` support              |
| `fieldHasMaxDepth`          | Field supports population depth control                     | Control relationship/upload/join depth   |
| `fieldIsPresentationalOnly` | Field is UI-only (no data storage)                          | 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 handling                           | Proper locale table checks               |
| `fieldIsVirtual`            | Field is virtual (computed/no DB column)                    | 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}` format                           | 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         |

```ts
import { fieldAffectsData, fieldHasSubFields, fieldIsArrayType } from 'payload'

function processField(field: Field) {
  if (fieldAffectsData(field)) {
    // Safe to access field.name
    console.log(field.name)
  }

  if (fieldHasSubFields(field)) {
    // Safe to access field.fields
    field.fields.forEach(processField)
  }
}
```

See [FIELD-TYPE-GUARDS.md](FIELD-TYPE-GUARDS.md) for detailed usage patterns.
