--- title: "Blocks Field" aliases: [blocks-field, payload-blocks, layout-builder] tags: [payloadcms, fields, blocks, layout-builder, lexical] sources: [raw/fields__blocks.md] created: 2026-05-15 updated: 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|Array Field]] (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`) ```ts 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: true` gotcha** — 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 on `null`. Use a custom `validate` function instead for within-document uniqueness. --- ## Block Field Admin Options ```ts admin: { initCollapsed: true, // start collapsed isSortable: false, // disable drag reorder } ``` ### Lexical Custom Rendering When used inside [[wiki/payloadcms/rich-text|Rich Text (Lexical)]], you can customize block display: - `admin.components.Label` — custom React component for the label - `admin.components.Block` — completely replace the block rendering in Lexical Import utility components from `@payloadcms/richtext-lexical/client`: ```ts 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 ```ts 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 SVG - `thumbnail`: 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 `localStorage` under `_payloadClipboard` - Persists across tabs (same origin), not across different Payload origins - Validates target field's `blocks` config 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: ```ts 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: ```ts { 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 `blockReferences` when the same block appears in 3+ places - Use `interfaceName` on blocks for clean TypeScript interfaces - Prefer `admin.disableBlockName` for programmatic blocks where editors don't need to name rows - For within-document uniqueness, use `validate` — not `unique: true` --- ## Example: Full Collection with Blocks ```ts 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|Array Field]] - Each block is identified by `slug` (stored as `blockType`); editors can add a `blockName` per instance - `filterOptions` enables conditional availability based on form state - **Copy/paste** is cross-document; **Duplicate** is in-place only — do not confuse them - `blockReferences` optimizes performance when blocks are reused across many collections/lexical editors - `unique: true` on a nested block field is collection-wide, not per-document — use `validate` instead - 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](https://payloadcms.com/docs/fields/blocks) ## Related - [[wiki/payloadcms/fields-array|Array Field]] — homogeneous repeating rows - [[wiki/payloadcms/fields-complex|Fields: Complex]] — all structural fields overview - [[wiki/payloadcms/rich-text|Rich Text (Lexical)]] — Blocks feature inside Lexical editor - [[wiki/payloadcms/database-indexes|Database Indexes]] — `unique` field gotchas - [[wiki/payloadcms/typescript|TypeScript]] — `interfaceName`, type generation