Payload CMS fields: every field type and how to choose one

Idea Labz · Nov 13, 2026 · 15 min read

You’re writing a collection in Payload and you need to know what each field type stores, what your editors will see, and which one fits. The field list is the whole data model, so the choice decides your database columns, your API responses and your admin screens at once.

As of 12 November 2026, the latest stable release, Payload 3.93.0, ships 22 field types. Payload’s fields documentation gives each one its own page. This guide puts them side by side, then covers the options every field shares, layout fields, virtual fields, ordering, how to choose, and what Payload 4 changes.

We build client projects on Payload from our own project template, and every page in it is built from a blocks field.

  • How fields work in Payload
  • Every Payload field type at a glance
  • Fields that hold one value
  • Fields that link documents: relationship, upload and join
  • The rich text field
  • Arrays and blocks for repeating content
  • Group, tabs, row and collapsible: arranging the admin
  • UI fields, custom components and colour pickers
  • Virtual fields
  • Options every field shares
  • Letting editors sort and reorder
  • How to choose a field type
  • What changes for fields in Payload 4
  • How we use fields on our builds
  • Frequently asked questions
  • What to do next

How fields work in Payload

A field is one entry in a collection’s or a global’s fields array, and that one entry does four jobs. It defines a database column (or a nested value on MongoDB), an input in the admin panel, a key in every API response, and a property in your generated TypeScript types. Every field has a type, and every field that stores data also has a name.

Payload sorts its types into three kinds. Data fields store a value. Presentational fields, such as a row or an unnamed tab, only arrange the admin. Virtual fields show a value that isn’t stored. Fields behave the same way in a collection and in a global.

For example, take a group of three boutique hotels in Dubai that wants one Payload backend for its rooms, its hotels and its website pages. The rooms collection starts with three fields:

import type { CollectionConfig } from 'payload'

export const Rooms: CollectionConfig = {
  slug: 'rooms',
  admin: { useAsTitle: 'name' },
  fields: [
    { name: 'name', type: 'text', required: true },
    { name: 'maxGuests', type: 'number', min: 1, max: 6 },
    {
      name: 'bedType',
      type: 'select',
      options: ['king', 'queen', 'twin'],
    },
  ],
}

Editors now see a text box, a number input and a dropdown, and the API returns name, maxGuests and bedType on every room. If you haven’t set up a project yet, our Payload CMS tutorial builds the first collection from a blank template.

Every Payload field type at a glance

The table lists all 22 types in payload@3.93.0, grouped by kind. "Stores" is what lands in the database; "Admin shows" is what an editor works with.

TypeKindStoresAdmin showsUse it for
textdataa string, or a list of strings with hasManya text inputnames, titles, slugs
textareadataa stringa multi-line boxshort plain descriptions
emaildataa string, checked as an emailan email inputcontact addresses
numberdataa number, or a list with hasManya number inputprices, counts, ratings
checkboxdatatrue or falsea checkboxflags such as "featured"
selectdataone option, or several with hasManya dropdowna fixed list of choices
radiodataone optiona radio grouptwo to five visible choices
datedataa date and timea date pickerpublish dates, opening times
pointdataa longitude and latitude pairtwo number inputslocations for map queries
codedataa stringa code editorsnippets, embed code
jsondataany JSON valuea JSON editorsettings, imported data
relationshipdataa link to one or more documentsa searchable pickerauthors, categories, related items
uploaddataa link to a document in an upload collectiona file picker with a previewimages and files
richTextdataformatted content as JSONa rich text editorarticle bodies
arraydataa list of rows with the same fieldsrepeatable rowsamenities, opening hours
blocksdataa list of rows, each one of several shapesa block pickerpage layouts
groupdata when nameda nested objecta titled set of fieldsSEO settings, addresses
tabsdata when a tab is nameda nested object per named tabtabssplitting long forms
rowlayoutnothingfields side by sideshort related inputs
collapsiblelayoutnothinga panel editors can fold awayoptional or advanced settings
uilayoutnothingyour own componentpreviews, buttons, help
joinvirtualnothinga list of documents that point herea hotel’s rooms

Fields that hold one value

Text and numbers

text, textarea and email store strings. text takes minLength and maxLength, and hasMany: true turns it into a list of strings, which suits tags typed by hand. number takes min and max, and it also accepts hasMany.

Slugs aren’t a field type. Payload exports a slugField() helper that adds a text field and a hidden checkbox, and by default it fills the slug from title on save and puts both in the sidebar.

Choices

select and radio both take an options list. select renders a dropdown and accepts hasMany: true for several choices, while radio shows every option at once and stores one. A checkbox stores true or false, and in 3.93.0 a required checkbox with no defaultValue gets false as its default.

For example, the hotel’s rooms use a select for the bed type and a seaView checkbox.

Dates and time zones

A date field stores a full date and time. admin.date.pickerAppearance sets what the editor picks, from dayOnly (the default) to dayAndTime, timeOnly or monthOnly.

Set timezone: true and Payload adds a second, hidden field named after the first with _tz on the end, which stores the time zone the editor chose. For example, each hotel’s check-in time:

{
  name: 'checkInTime',
  type: 'date',
  timezone: { defaultTimezone: 'Asia/Dubai' },
  admin: { date: { pickerAppearance: 'timeOnly' } },
}

Location, code and JSON

point stores coordinates and gets a 2dsphere index by default, so you can query by distance. It isn’t supported on SQLite, as our Payload database guide notes. code stores a string in a code editor with syntax highlighting for the language you set, and json stores any JSON value in a JSON editor.

Fields that link documents: relationship, upload and join

A relationship field stores a reference to another document. Point relationTo at one collection, or at an array of collections for a polymorphic link, and add hasMany: true for several. filterOptions limits what the picker offers. For example, a room links to its hotel:

{
  name: 'hotel',
  type: 'relationship',
  relationTo: 'hotels',
  required: true,
}

An upload field works the same way, but only for collections set up for uploads, such as media. It stores a reference to the media document and shows a preview of the file.

A join field stores nothing. It lists the documents in another collection whose relationship points at this one. For example, the hotels collection can show every room that belongs to it:

{
  name: 'rooms',
  type: 'join',
  collection: 'rooms',
  on: 'hotel',
}

The link lives only on the room, so there’s one source of truth, and the hotel’s edit view still shows its rooms.

The rich text field

A richText field stores the editor’s document as JSON. The editor comes from the config’s editor setting or the field’s own, and in 3.93.0 a rich text field with neither stops the config loading. Lexical (@payloadcms/richtext-lexical) is Payload’s own editor and the only one supported from 4.0, and you choose what it can do through the features you pass. For example, a table inside an article comes from the editor’s table feature, which 3.93.0 exports as EXPERIMENTAL_TableFeature.

Rich text suits content a writer formats. If the parts have meaning of their own, such as a price, a date or a link to a room, give them their own fields so the API can filter and sort by them.

Arrays and blocks for repeating content

An array holds rows that share one shape. Each row has the fields you list, and minRows and maxRows set the limits. For example, each room’s amenities:

{
  name: 'amenities',
  type: 'array',
  maxRows: 12,
  fields: [{ name: 'label', type: 'text', required: true }],
}

A blocks field holds rows too, but each row picks its shape from a list of blocks. That makes it the usual way to build pages, since an editor stacks a hero, some text and a call to action in any order:

{
  name: 'layout',
  type: 'blocks',
  minRows: 1,
  blocks: [
    {
      slug: 'hero',
      fields: [
        { name: 'heading', type: 'text', required: true },
        { name: 'image', type: 'upload', relationTo: 'media' },
      ],
    },
    {
      slug: 'roomList',
      fields: [{ name: 'rooms', type: 'relationship', relationTo: 'rooms', hasMany: true }],
    },
  ],
}

Each stored row records its blockType. On MongoDB, arrays and blocks sit inside the document. On Postgres and SQLite, each array gets a table of its own and so does each block type, unless you set the adapter’s blocksAsJSON option, which stores blocks as one JSON column. Our guide to Payload’s databases shows what that looks like for a real collection.

You can define blocks once in the config’s top-level blocks and reference them by slug with blockReferences, which keeps one shared definition across collections. If the rows are things other pages reuse, such as rooms, keep them in their own collection and link them with a relationship.

Group, tabs, row and collapsible: arranging the admin

row puts fields side by side, and each field’s admin.width sets its share. collapsible puts fields in a panel that opens and closes. Neither stores anything.

group and tabs work both ways. Without a name, they only arrange the admin, and the fields inside are stored at the top level. With a name, they store a nested object. For example, a pages collection with Content and SEO tabs:

{
  type: 'tabs',
  tabs: [
    {
      label: 'Content',
      fields: [{ name: 'title', type: 'text', required: true }],
    },
    {
      name: 'seo',
      label: 'SEO',
      fields: [{ name: 'metaTitle', type: 'text' }],
    },
  ],
}

The API returns title at the top level and seo.metaTitle inside an object, because only the second tab has a name.

In other words, adding a name to an existing tab or group moves its data to a new key. On Postgres and SQLite that changes the columns, so plan it as a migration before it reaches production.

UI fields, custom components and colour pickers

A ui field stores nothing and renders a component you write in the edit view or the list view. It still needs a name, and its component goes in admin.components.Field. For example, the hotel group could show a live booking-widget preview beside each room.

Any data field can also swap its input. Set admin.components.Field to your own component and the field keeps its type, validation and storage while editors see your interface. Our Payload admin panel guide covers custom components in more depth.

How to add a colour picker

Payload 3.93.0 has no colour field type. You get one in one of two ways:

  1. A text field that stores the hex value, with a custom Field component that renders a picker. Payload’s docs on swapping in your own React components cover the pattern.
  2. A community plugin that does the same thing for you. Several exist; check that one supports your Payload version before you install it.

For example, each hotel’s accent colour for its microsite is a text field with a picker on top. Colour inside rich text comes from the editor’s TextStateFeature instead, set on the rich text field.

Virtual fields

A virtual field appears in API responses and the admin but isn’t stored. Any field type becomes virtual with the virtual option, which takes two forms.

virtual: true leaves the value empty, and you fill it in a field hook, usually afterRead. For example, a room’s display label built from its name and bed type. virtual set to a path reads a value through a relationship:

{ name: 'hotelName', type: 'text', virtual: 'hotel.name' }

Now each room returns its hotel’s name, and editors can see it in the list view without opening the hotel.

In 3.93.0, virtual fields are read-only in the admin unless you set admin.readOnly: false. A path virtual field can be used in queries and sorts, because Payload rewrites it to the path. A virtual: true field can’t be queried, since there’s nothing stored to filter on.

Options every field shares

Every type takes these options on top of its own:

OptionWhat it does
nameThe key in the database and the API
labelThe text editors see; generated from the name if you leave it out
requiredRejects a save without a value
uniqueRejects a value another document already has
indexAdds a database index for faster queries
defaultValueA value, or a function that returns one, for new documents
validateYour own check, which returns true or an error message
localizedStores one value per language
hiddenKeeps the value in the database but out of every API and the admin
accesscreate, read and update rules for this field alone
hooksbeforeValidate, beforeChange, afterChange, afterRead and beforeDuplicate
admincondition, description, position, readOnly, width and component swaps

For example, the rooms collection makes name required and gives maxGuests a defaultValue of 2.

localized is covered in our Payload localisation guide, field-level access in our authentication guide, and hooks as a whole in the Payload guide’s hooks section. Turning on drafts adds a _status field of its own, which our drafts and versions guide explains.

Field names to avoid

Names become keys and columns, so keep them to letters, numbers and underscores. In 3.93.0 the config throws an InvalidFieldName error for a name with a dot in it, and a ReservedFieldName error at start-up for three top-level names. These are file in an upload collection, and salt or hash in an auth collection with local login. For example, a field named file on the hotel group’s media collection stops Payload starting. Payload’s docs also list status (on Postgres with drafts on) and __v as reserved, so avoid both.

Letting editors sort and reorder

Editors drag rows in an array or a blocks field to reorder them, which is on by default. Set admin.isSortable: false to stop it. A hasMany relationship field becomes draggable when you set admin.isSortable: true.

Payload added drag-and-drop ordering for whole documents in 3.32.0 (April 2025). Set orderable: true on a collection and editors drag documents into order in the list view, while the API can set the same order. Payload adds a hidden, indexed _order field that holds each position as a fractional index, and sorts by it unless you set another default. That’s a new column on Postgres and SQLite, so it needs a migration. The option is marked experimental in the 3.93.0 types. A join field takes orderable: true as well, for ordering the documents it lists.

For example, the hotel group orders its rooms by hand so the suites come first on each hotel’s page. Sorting a query is a separate setting. Pass a field name to the sort parameter, with a minus sign in front for descending order.

How to choose a field type

Work through the questions for each piece of data. For example, the rooms collection:

  1. One value or many? Maximum guests is one number. Tags typed by hand are a text field with hasMany.
  2. A fixed list or free input? The bed type has three options, so it’s a select. A name is free text.
  3. Data of its own, or another document? The hotel is a document with its own fields, so it’s a relationship. A photo is a document in the media collection, so it’s an upload.
  4. Rows of one shape, or of several? Amenities all have a label, so they’re an array. A page mixes heroes and room lists, so it’s blocks.
  5. Stored or worked out? The hotel’s name already lives on the hotel, so the room reads it through a virtual field.
  6. Data or layout? An SEO group that the front end reads is a named group or tab. Fields placed side by side for editors are a row.

The finished collection:

import type { CollectionConfig } from 'payload'

export const Rooms: CollectionConfig = {
  slug: 'rooms',
  admin: { useAsTitle: 'name' },
  fields: [
    {
      type: 'row',
      fields: [
        { name: 'name', type: 'text', required: true, admin: { width: '60%' } },
        { name: 'maxGuests', type: 'number', min: 1, max: 6, admin: { width: '40%' } },
      ],
    },
    { name: 'bedType', type: 'select', options: ['king', 'queen', 'twin'] },
    { name: 'seaView', type: 'checkbox' },
    { name: 'hotel', type: 'relationship', relationTo: 'hotels', required: true },
    { name: 'hotelName', type: 'text', virtual: 'hotel.name' },
    { name: 'photos', type: 'upload', relationTo: 'media', hasMany: true },
    {
      name: 'amenities',
      type: 'array',
      fields: [{ name: 'label', type: 'text', required: true }],
    },
  ],
}

What changes for fields in Payload 4

Payload 4 is in beta, and the 3.0 to 4.0 migration guide lists these changes for fields as of 4.0.0-beta.8 (10 November 2026). Betas can still change before 4.0 is stable, and our Payload review tracks the release.

  • blockReferences is gone. The blocks property takes both inline blocks and the slugs of config-level blocks, and a codemod moves you over.
  • min and max on hasMany relationship and upload fields are removed. Use minRows and maxRows.
  • typescriptSchema is renamed jsonSchema.
  • Every block gets its own TypeScript interface, named from its slug, and interfaceName only overrides the name.
  • Queries without depth populate one level of relationships, down from two.
  • Collections and globals gain createdBy and updatedBy fields by default, which on Postgres and SQLite needs a migration.
  • The rich text table feature drops its prefix and becomes TableFeature.

The admin side of 4.0 is in our admin panel guide’s Payload 4 section.

How we use fields on our builds

Our project template (v2.3.4, November 2026, on Payload 3.90.1) is what our client projects start from. In its shared collections, globals and page blocks, text carries most of the content by far, then checkbox, select, group and array, then upload, textarea and number, then a few relationships.

A page is a title, a slug, a publish date and one required blocks field. The template ships five content blocks (hero, rich text content, media, call to action and a listing block), defined inline on the page layout, and each project can register its own. Editors drag blocks and array rows into order, which is Payload’s default. The Content and SEO tabs on pages come from Payload’s SEO plugin.

We don’t use slugField(). Pages and tenants each have a plain text slug field with a field hook that builds it from the title or name when it’s left empty. Our own field definitions use no join, point, code or radio fields, and no collection uses orderable. One of our custom field components wraps json fields so the code editor always loads from our own copy, never a CDN.

Some fields have to be added by people who don’t edit code. Our dynamic data plugin, one of the plugins we’ve written, lets a tenant admin pick from 13 field types, from text and date to relationship, attachment and formula. The plugin stores those definitions as data, so a new field needs no deploy and no migration.

If you’d like a data model shaped around your content before the build starts, our Payload CMS development team can help.

Frequently asked questions

How many field types does Payload have?

Payload 3.93.0 has 22 field types. Eighteen store data (group and tabs only when named), three only arrange the admin (row, collapsible and ui), and join shows related documents without storing them. Any type can also be made virtual with the virtual option.

Does Payload have a colour picker field?

No, there’s no colour type in Payload 3.93.0. Use a text field that stores the hex value with a custom field component that renders a picker, or a community plugin that adds one. For colour inside rich text, use the Lexical TextStateFeature.

Is there a table field in Payload?

No. For rows of structured data, use an array field with one field per column. For a table inside an article, add the rich text editor’s table feature. The admin’s list view already shows each collection as a table you can sort and filter.

What’s the difference between a named and an unnamed tab?

An unnamed tab only groups fields in the admin, and its fields are stored at the top level of the document. A named tab stores its fields inside an object under that name. Adding a name later changes where the data lives, so treat it as a schema change.

How do I add a slug field in Payload?

Use the slugField() helper from payload, which adds a unique, indexed text field in the sidebar and fills it from the title field on save. You can change the source field, the name and the slug function through its options. A plain text field with a beforeValidate hook does the same job if you want full control.

Can editors reorder documents by drag and drop?

Yes, set orderable: true on the collection and editors drag documents into order in the list view. The option is marked experimental in the 3.93.0 types. Rows inside array and blocks fields are reorderable by default.

What is a virtual field in Payload?

A virtual field shows a value in the API and the admin without storing it. Set virtual: true and fill the value in an afterRead hook, or set virtual to a path such as hotel.name to read it through a relationship. Only the path form can be queried and sorted.

Can a field be required only in some cases?

Yes, with a validate function that checks the other fields and returns an error message when the value is missing. To hide the field when it doesn’t apply, add admin.condition, which receives the document’s data and the field’s siblings.

What to do next

Start each collection by listing the questions it has to answer, then pick a type from the table for each one. Keep layout fields unnamed unless the front end needs the data grouped, and treat every rename or new name as a migration. Reach for blocks when editors build pages, and a relationship when the rows are things in their own right. Before you move to Payload 4, check the field changes above against your config.

If you’re starting from scratch, build a first collection with our tutorial. If your content runs in more than one language, read how localisation works in Payload before you set localized on anything.

References

All links checked and active as of 12 November 2026.

READY TO MAKE A REAL CHANGE?

Let's build it together