Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Blocks Field

The Blocks Field is one of the most flexible tools in Payload. It stores an array of objects, where each object is a “block” with its own schema. Unlike a simple array (where every item looks the same), blocks let you mix and match different content types in any order.

This makes Blocks perfect for building dynamic, editor-friendly experiences, such as:

  • A page builder with blocks like Quote, CallToAction, Slider, or Gallery.
  • A form builder with block types like Text, Select, or Checkbox.
  • An event agenda where each timeslot could be a Break, Presentation, or BreakoutSession.
Admin Panel screenshot of add Blocks drawer view
Admin Panel screenshot of add Blocks drawer view

To add a Blocks Field, set the type to blocks in your Field Config:

1
import type { Field } from 'payload'
2
3
export const MyBlocksField: Field = {
4
// ...
5
type: 'blocks',
6
blocks: [
7
// ...
8
],
9
}

This page is divided into two parts: first, the settings of the Blocks Field, and then the settings of the blocks inside it.

Block Field

Config Options

Option

Description

name *

To be used as the property name when stored and retrieved from the database. More details.

label

Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined.

blocks *

Array of block configs to be made available to this field.

validate

Provide a custom validation function that will be executed on both the Admin Panel and the backend. More details.

minRows

A number for the fewest allowed items during validation when a value is present.

maxRows

A number for the most allowed items during validation when a value is present.

saveToJWT

If this field is top-level and nested in a config supporting Authentication, include its data in the user JWT.

hooks

Provide Field Hooks to control logic for this field. More details.

access

Provide Field Access Control to denote what users can see and do with this field's data. More details.

hidden

Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel.

defaultValue

Provide an array of block data to be used for this field's default value. More details.

localized

Enable localization for this field. Requires localization to be enabled in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as localized.

unique

Enforce that each entry in the Collection has a unique value for this field.

labels

Customize the block row labels appearing in the Admin dashboard.

admin

Admin-specific configuration. More details.

custom

Extension point for adding custom data (e.g. for plugins)

typescriptSchema

Override field type generation with providing a JSON schema

virtual

Provide true to disable field in the database, or provide a string path to link the field with a relationship. See Virtual Fields

* An asterisk denotes that a property is required.

Admin Options

To customize the appearance and behavior of the Blocks Field in the Admin Panel, you can use the admin option:

1
import type { Field } from 'payload'
2
3
export const MyBlocksField: Field = {
4
// ...
5
admin: {
6
7
// ...
8
},
9
}

The Blocks Field inherits all of the default admin options from the base Field Admin Config, plus the following additional options:

Option

Description

initCollapsed

Set the initial collapsed state

isSortable

Disable order sorting by setting this value to false

Customizing the way your block is rendered in Lexical

If you're using this block within the Lexical editor, you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.

  • admin.components.Label - pass a custom React component here to customize the way that the label is rendered for this block
  • admin.components.Block - pass a component here to completely override the way the block is rendered in Lexical with your own component

This is super handy if you'd like to present your editors with a very deliberate and nicely designed block "preview" right in your rich text.

For example, if you have a gallery block, you might want to actually render the gallery of images directly in your Lexical block. With the admin.components.Block property, you can do exactly that!

To import these utility components for one of your custom blocks, you can import the following:

1
import {
2
// Edit block buttons (choose the one that corresponds to your usage)
3
// When clicked, this will open a drawer with your block's fields
4
// so your editors can edit them
5
InlineBlockEditButton,
6
BlockEditButton,
7
8
// Buttons that will remove this block from Lexical
9
// (choose the one that corresponds to your usage)
10
InlineBlockRemoveButton,
11
BlockRemoveButton,
12
13
// The label that should be rendered for an inline block
14
InlineBlockLabel,
15
16
// The default "container" that is rendered for an inline block
17
// if you want to re-use it
18
InlineBlockContainer,
19
20
// The default "collapsible" UI that is rendered for a regular block
21
// if you want to re-use it
22
BlockCollapsible,
23
} from '@payloadcms/richtext-lexical/client'

Blocks Items

Config Options

Blocks are defined as separate configs of their own.

Option

Description

slug *

Identifier for this block type. Will be saved on each block as the blockType property.

fields *

Array of fields to be stored in this block.

labels

Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. Alternatively you can use admin.components.Label for greater control.

imageURL

Provide a custom image thumbnail to help editors identify this block in the Admin UI.

imageAltText

Customize this block's image thumbnail alt text.

interfaceName

Create a top level, reusable Typescript interface & GraphQL type.

graphQL.singularName

Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer interfaceName.

dbName

Custom table name for this block type when using SQL Database Adapter (Postgres). Auto-generated from slug if not defined.

custom

Extension point for adding custom data (e.g. for plugins)

Admin Options

Blocks are not fields, so they don’t inherit the base properties shared by all fields (not to be confused with the Blocks Field, documented above, which does). Here are their available admin options:

Option

Description

components.Block

Custom component for replacing the Block, including the header.

components.Label

Custom component for replacing the Block Label.

disableBlockName

Hide the blockName field by setting this value to true.

group

Text or localization object used to group this Block in the Blocks Drawer.

custom

Extension point for adding custom data (e.g. for plugins)

blockType, blockName, and block.label

Each block stores two pieces of data alongside your fields. The blockType identifies which schema to use and it is exactly the block’s slug. The blockName is an optional label you can give to a block to make editing and scanning easier.

The label is shared by all blocks of the same type and is defined in the block config via label with a fallback to slug. On the other hand, the blockName is specific to each block individually. You can hide the editable name with admin.disableBlockName.

If you provide admin.components.Label, that component replaces both the name and the label in the Admin UI.

Property

Scope

Source

Visible in UI

Notes

blockType

Each block

The block’s slug

Not a header

Used to resolve which block schema to render

blockName

Each block

Editor input in the Admin

Yes

Optional label; hide with admin.disableBlockName or replace with custom admin.components.Label

block.label

Block type

label in block config or slug fallback

Yes

Shared by all blocks of that type. Can be replaced with custom admin.components.Label

Custom Components

Field

Server Component
1
import type React from 'react'
2
import { BlocksField } from '@payloadcms/ui'
3
import type { BlocksFieldServerComponent } from 'payload'
4
5
export const CustomBlocksFieldServer: BlocksFieldServerComponent = ({
6
clientField,
7
path,
8
schemaPath,
9
permissions,
10
}) => {
11
return (
12
<BlocksField
13
field={clientField}
14
path={path}
15
schemaPath={schemaPath}
16
permissions={permissions}
17
/>
18
)
19
}
Client Component
1
'use client'
2
import React from 'react'
3
import { BlocksField } from '@payloadcms/ui'
4
import type { BlocksFieldClientComponent } from 'payload'
5
6
export const CustomBlocksFieldClient: BlocksFieldClientComponent = (props) => {
7
return <BlocksField {...props} />
8
}

Label

Server Component
1
import React from 'react'
2
import { FieldLabel } from '@payloadcms/ui'
3
import type { BlocksFieldLabelServerComponent } from 'payload'
4
5
export const CustomBlocksFieldLabelServer: BlocksFieldLabelServerComponent = ({
6
clientField,
7
path,
8
}) => {
9
return (
10
<FieldLabel
11
label={clientField?.label || clientField?.name}
12
path={path}
13
required={clientField?.required}
14
/>
15
)
16
}
Client Component
1
'use client'
2
import React from 'react'
3
import { FieldLabel } from '@payloadcms/ui'
4
import type { BlocksFieldLabelClientComponent } from 'payload'
5
6
export const CustomBlocksFieldLabelClient: BlocksFieldLabelClientComponent = ({
7
label,
8
path,
9
required,
10
}) => {
11
return (
12
<FieldLabel
13
label={field?.label || field?.name}
14
path={path}
15
required={field?.required}
16
/>
17
)
18
}

Example

collections/ExampleCollection.js

1
import { Block, CollectionConfig } from 'payload'
2
3
const QuoteBlock: Block = {
4
slug: 'Quote', // required
5
imageURL: 'https://google.com/path/to/image.jpg',
6
imageAltText: 'A nice thumbnail image to show what this block looks like',
7
interfaceName: 'QuoteBlock', // optional
8
fields: [
9
// required
10
{
11
name: 'quoteHeader',
12
type: 'text',
13
required: true,
14
},
15
{
16
name: 'quoteText',
17
type: 'text',
18
},
19
],
20
}
21
22
export const ExampleCollection: CollectionConfig = {
23
slug: 'example-collection',
24
fields: [
25
{
26
name: 'layout', // required
27
type: 'blocks', // required
28
minRows: 1,
29
maxRows: 20,
30
blocks: [
31
// required
32
QuoteBlock,
33
],
34
},
35
],
36
}

Block References

If you have multiple blocks used in multiple places, your Payload Config can grow in size, potentially sending more data to the client and requiring more processing on the server. However, you can optimize performance by defining each block once in your Payload Config and then referencing its slug wherever it's used instead of passing the entire block config.

To do this, define the block in the blocks array of the Payload Config. Then, in the Blocks Field, pass the block slug to the blockReferences array - leaving the blocks array empty for compatibility reasons.

1
import { buildConfig } from 'payload'
2
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'
3
4
// Payload Config
5
const config = buildConfig({
6
// Define the block once
7
blocks: [
8
{
9
slug: 'TextBlock',
10
fields: [
11
{
12
name: 'text',
13
type: 'text',
14
},
15
],
16
},
17
],
18
collections: [
19
{
20
slug: 'collection1',
21
fields: [
22
{
23
name: 'content',
24
type: 'blocks',
25
// Reference the block by slug
26
blockReferences: ['TextBlock'],
27
blocks: [], // Required to be empty, for compatibility reasons
28
},
29
],
30
},
31
{
32
slug: 'collection2',
33
fields: [
34
{
35
name: 'editor',
36
type: 'richText',
37
editor: lexicalEditor({
38
features: [
39
BlocksFeature({
40
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
41
blocks: ['TextBlock'],
42
}),
43
],
44
}),
45
},
46
],
47
},
48
],
49
})

TypeScript

As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's Block type:

1
import type { Block } from 'payload'
Next

Checkbox Field