9.3 KiB
| title | aliases | tags | sources | created | updated | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Blocks Field |
|
|
|
2026-05-15 | 2026-05-15 |
Overview
The Blocks Field stores an array of objects where each object is a "block" with its own schema. Unlike wiki/payloadcms/fields-array (every item looks the same), blocks let you mix and match different content types in any order.
Primary use-cases:
- Page builder (
Quote,CallToAction,Slider,Gallery) - Form builder (
Text,Select,Checkbox) - Event agenda (
Break,Presentation,BreakoutSession)
import type { Field } from 'payload'
export const MyBlocksField: Field = {
name: 'layout',
type: 'blocks',
blocks: [QuoteBlock, CTABlock],
}
Block Field Config Options
| Option | Required | Description |
|---|---|---|
name |
✅ | Property name in DB |
blocks |
✅ | Array of block configs available to this field |
minRows |
— | Minimum block count during validation |
maxRows |
— | Maximum block count during validation |
filterOptions |
— | Function returning allowed block slugs based on context |
defaultValue |
— | Array of block data for initial state |
localized |
— | Localizes all nested data without per-field localized: true |
validate |
— | Custom validation function (runs client + server) |
saveToJWT |
— | Include in user JWT if top-level auth field |
hooks |
— | Field-level hooks |
access |
— | Field-level access control |
labels |
— | Customize block row labels in Admin |
unique |
— | DB-level unique index (see gotcha below) |
hidden |
— | Hide from APIs but still save to DB |
virtual |
— | Disable DB storage or link via string path |
typescriptSchema |
— | Override generated TS type with JSON schema |
⚠️
unique: truegotcha — creates a collection-wide DB unique index, not per-document. No two documents can share the same value at that nested path. On MongoDB, documents without the block collide onnull. Use a customvalidatefunction instead for within-document uniqueness.
Block Field Admin Options
admin: {
initCollapsed: true, // start collapsed
isSortable: false, // disable drag reorder
}
Lexical Custom Rendering
When used inside wiki/payloadcms/rich-text, you can customize block display:
admin.components.Label— custom React component for the labeladmin.components.Block— completely replace the block rendering in Lexical
Import utility components from @payloadcms/richtext-lexical/client:
import {
InlineBlockEditButton, BlockEditButton,
InlineBlockRemoveButton, BlockRemoveButton,
InlineBlockLabel, InlineBlockContainer,
BlockCollapsible,
} from '@payloadcms/richtext-lexical/client'
Block Config Options (per block)
Each block is its own config object:
| Option | Required | Description |
|---|---|---|
slug |
✅ | Identifies block type; saved as blockType on each block |
fields |
✅ | Array of fields for this block |
labels |
— | Admin label (auto-generated from slug if omitted) |
interfaceName |
— | Creates reusable TypeScript interface + GraphQL type |
dbName |
— | Custom SQL table name (Postgres) |
graphQL.singularName |
— | GraphQL schema name (deprecated; prefer interfaceName) |
custom |
— | Plugin extension point |
Block Admin Options
| Option | Description |
|---|---|
components.Block |
Replace entire block including header |
components.Label |
Replace block label only |
disableBlockName |
Hide the blockName field (true to hide) |
group |
Group blocks in the selection drawer |
images.icon |
20×20px icon for Lexical menus (SVG preferred) |
images.thumbnail |
3:2 ratio thumbnail for block selection drawer |
Block Image Guidelines
const QuoteBlock: Block = {
slug: 'quote',
admin: {
images: {
icon: { url: 'https://example.com/icons/quote-20x20.svg', alt: 'Quote' },
thumbnail: { url: 'https://example.com/thumbnails/quote-480x320.jpg', alt: 'Quote block' },
},
},
fields: [{ name: 'quoteText', type: 'text', required: true }],
}
icon: 1:1 aspect ratio, 20×20px display — use SVGthumbnail: 3:2 aspect ratio (480×320, 600×400, 900×600) — use centered important content
blockType, blockName, block.label
| Property | Scope | Source | Editable |
|---|---|---|---|
blockType |
Each block | Block's slug |
No — auto-set |
blockName |
Each block | Editor input | Yes (hide with disableBlockName) |
block.label |
Block type | label in block config or slug fallback |
No — config only |
blockName is the per-instance label editors give to distinguish identical block types (e.g., two Quote blocks with different names). block.label is shared by all blocks of that type.
Copy / Paste
Built-in clipboard support via the ... row action menu:
| Action | Scope | Behavior |
|---|---|---|
| Copy Row | Single block | Paste into any position in any compatible field |
| Paste Row | Single block | Replaces a specific row |
| Copy Fields | Entire field | All blocks at once |
| Paste Fields | Entire field | Replaces ALL blocks in target field |
- Clipboard stored in
localStorageunder_payloadClipboard - Persists across tabs (same origin), not across different Payload origins
- Validates target field's
blocksconfig before allowing paste - Block IDs automatically regenerated on paste to prevent duplicates
⚠️ Paste Fields replaces everything — even if you copied a single row, "Paste Fields" replaces all blocks. To add a copied row to an existing field: add an empty block first, then use Paste Row on that empty block.
Block References (Performance Optimization)
Define a block once in root blocks config and reference it by slug to avoid sending the full schema to the client multiple times:
const config = buildConfig({
blocks: [
{
slug: 'TextBlock',
fields: [{ name: 'text', type: 'text' }],
},
],
collections: [
{
slug: 'collection1',
fields: [{
name: 'content',
type: 'blocks',
blockReferences: ['TextBlock'],
blocks: [], // must be empty when using blockReferences
}],
},
],
})
⚠️ Block references are isolated — the block config cannot be modified per collection; access control runs without collection data.
Conditional Blocks (filterOptions)
Dynamically restrict which blocks are available based on sibling data:
{
name: 'layout',
type: 'blocks',
filterOptions: ({ siblingData }) => {
return siblingData?.enabledBlocks?.length
? [siblingData.enabledBlocks]
: true // allow all
},
blocks: [block1, block2, block3],
}
- Re-evaluated on every form state update
- Blocks present in data but disallowed by
filterOptions→ validation error on save
Best Practices
- Define each block config in its own file for reusability across collections
- Use
blockReferenceswhen the same block appears in 3+ places - Use
interfaceNameon blocks for clean TypeScript interfaces - Prefer
admin.disableBlockNamefor programmatic blocks where editors don't need to name rows - For within-document uniqueness, use
validate— notunique: true
Example: Full Collection with Blocks
import { Block, CollectionConfig } from 'payload'
const QuoteBlock: Block = {
slug: 'Quote',
interfaceName: 'QuoteBlock',
fields: [
{ name: 'quoteHeader', type: 'text', required: true },
{ name: 'quoteText', type: 'text' },
],
}
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [{
name: 'layout',
type: 'blocks',
minRows: 1,
maxRows: 20,
blocks: [QuoteBlock],
}],
}
Key Takeaways
- Blocks Field = heterogeneous array — each item can be a different schema, unlike wiki/payloadcms/fields-array
- Each block is identified by
slug(stored asblockType); editors can add ablockNameper instance filterOptionsenables conditional availability based on form state- Copy/paste is cross-document; Duplicate is in-place only — do not confuse them
blockReferencesoptimizes performance when blocks are reused across many collections/lexical editorsunique: trueon a nested block field is collection-wide, not per-document — usevalidateinstead- Block images (
icon+thumbnail) improve the editor experience in Lexical and the block selection drawer - Full TypeScript support:
import type { Block } from 'payload'
Sources
raw/fields__blocks.md— payloadcms.com/docs/fields/blocks
Related
- wiki/payloadcms/fields-array — homogeneous repeating rows
- wiki/payloadcms/fields-complex — all structural fields overview
- wiki/payloadcms/rich-text — Blocks feature inside Lexical editor
- wiki/payloadcms/database-indexes —
uniquefield gotchas - wiki/payloadcms/typescript —
interfaceName, type generation