---
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
