---
title: "Rich Text — Blocks (Lexical BlocksFeature)"
aliases: [lexical-blocks, rich-text-blocks, blocksfeature]
tags: [payloadcms, rich-text, lexical, blocks, inline-blocks]
sources: [raw/rich-text__blocks.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

`BlocksFeature` embeds Payload's [[wiki/payloadcms/fields-blocks|Blocks Field]] directly inside the Lexical rich text editor. Blocks share the same config schema as standard Payload blocks — all field types, hooks, validation, access control, and conditional logic are supported. The only difference is that data is stored inside the rich text JSON, not as separate DB fields.

## Basic Setup

```ts
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'

{
  name: 'content',
  type: 'richText',
  editor: lexicalEditor({
    features: ({ defaultFeatures }) => [
      ...defaultFeatures,
      BlocksFeature({
        blocks: [
          {
            slug: 'banner',
            fields: [
              { name: 'style', type: 'select', options: ['info', 'warning', 'error', 'success'], defaultValue: 'info' },
              { name: 'content', type: 'textarea', required: true },
            ],
          },
        ],
      }),
    ],
  }),
}
```

## Blocks vs Inline Blocks

| | **Blocks** | **Inline Blocks** |
|--|--|--|
| Position | Block-level — occupy a full line | Flow within text (same paragraph) |
| Use cases | CTAs, galleries, code snippets, embeds | Mentions, badges, icons, placeholders, footnotes |
| Config key | `blocks: []` | `inlineBlocks: []` |

```ts
BlocksFeature({
  blocks: [
    { slug: 'callout', fields: [{ name: 'content', type: 'textarea' }] },
  ],
  inlineBlocks: [
    { slug: 'mention', fields: [{ name: 'user', type: 'relationship', relationTo: 'users', required: true }] },
  ],
})
```

## Data Structure

Block nodes in the Lexical JSON contain a `fields` object. Three fields are auto-included:

```json
{
  "type": "block",
  "version": 2,
  "fields": {
    "id": "65298b13db4ef8c744a7faaa",
    "blockType": "banner",
    "blockName": "Important Notice",
    "style": "warning",
    "content": "This is the block content..."
  }
}
```

Inline blocks use `"type": "inlineBlock"` but follow the same shape.

## Custom Block Components

Override how a block renders in the admin editor via `admin.components.Block`.

### Block Component (client)

```ts
{
  slug: 'myCustomBlock',
  admin: {
    components: {
      Block: '/path/to/MyBlockComponent#MyBlockComponent',
    },
  },
  fields: [{ name: 'style', type: 'select', options: ['primary', 'secondary'] }],
}
```

Composable primitives from `@payloadcms/richtext-lexical/client`:

| Primitive | Purpose |
|-----------|---------|
| `BlockCollapsible` | Collapsible wrapper with built-in edit drawer trigger |
| `BlockEditButton` | Opens field edit drawer |
| `BlockRemoveButton` | Removes the block from the editor |

```tsx
'use client'
import type { LexicalBlockClientProps } from '@payloadcms/richtext-lexical'
import { BlockCollapsible, BlockEditButton, BlockRemoveButton } from '@payloadcms/richtext-lexical/client'
import { useFormFields } from '@payloadcms/ui'

export const MyBlockComponent: React.FC<LexicalBlockClientProps> = () => {
  const style = useFormFields(([fields]) => fields.style)
  return (
    <BlockCollapsible removeButton={false}>
      <div>Style: {(style?.value as string) ?? 'none'}</div>
      <div style={{ display: 'flex' }}>
        <BlockEditButton />
        <BlockRemoveButton />
      </div>
    </BlockCollapsible>
  )
}
```

### Inline Block Component (client)

Primitives: `InlineBlockContainer`, `InlineBlockEditButton`, `InlineBlockLabel`, `InlineBlockRemoveButton` — all from `@payloadcms/richtext-lexical/client`.

### Label Components

Customize the block header label via `admin.components.Label`. Use `useFormFields` to render dynamic content from block field values.

## TypeScript Types

```ts
import type {
  LexicalBlockClientProps,
  LexicalBlockServerProps,
  LexicalBlockLabelClientProps,
  LexicalBlockLabelServerProps,
  LexicalInlineBlockClientProps,
  LexicalInlineBlockServerProps,
  LexicalInlineBlockLabelClientProps,
  LexicalInlineBlockLabelServerProps,
} from '@payloadcms/richtext-lexical'
```

## Pre-built CodeBlock

Payload ships a ready-made `CodeBlock` with syntax highlighting, language selection, and optional TypeScript IntelliSense:

```ts
import { BlocksFeature, CodeBlock } from '@payloadcms/richtext-lexical'

BlocksFeature({
  blocks: [
    CodeBlock({
      defaultLanguage: 'ts',
      languages: { plaintext: 'Plain Text', js: 'JavaScript', ts: 'TypeScript', tsx: 'TSX', jsx: 'JSX' },
    }),
  ],
})
```

### CodeBlock Options

| Option | Description |
|--------|-------------|
| `slug` | Override slug (default: `'Code'`) |
| `defaultLanguage` | Default language (default: first key) |
| `languages` | `{ key: label }` map |
| `typescript` | TypeScript IntelliSense config (see below) |
| `fieldOverrides` | Partial block config to extend defaults |

### TypeScript IntelliSense

```ts
CodeBlock({
  typescript: {
    fetchTypes: [
      { url: 'https://unpkg.com/payload@latest/dist/index.bundled.d.ts', filePath: 'file:///node_modules/payload/index.d.ts' },
    ],
    paths: { payload: ['file:///node_modules/payload/index.d.ts'] },
    typeRoots: ['node_modules/@types', 'node_modules/payload'],
    enableSemanticValidation: true,
  },
})
```

## Rendering Blocks on the Frontend

Blocks must be handled in your converter config:

- **JSX Converters** — React/Next.js: [[wiki/payloadcms/rich-text|Rich Text (Lexical)]] converting-jsx guide
- **HTML Converters** — static HTML output
- **Markdown Converters** — define custom block renderers

Each converter lets you define per-block-type custom renderers.

## Key Takeaways

- `BlocksFeature` accepts `blocks` (block-level) and `inlineBlocks` (inline) arrays — same config schema as the standard [[wiki/payloadcms/fields-blocks|Blocks Field]]
- Block data lives inside Lexical JSON; three auto-fields are always present: `id`, `blockType`, `blockName`
- Custom admin components use composable primitives (`BlockCollapsible`, `BlockEditButton`, etc.) from `@payloadcms/richtext-lexical/client`
- Use `useFormFields` (from `@payloadcms/ui`) to read live block field values inside custom components
- Pre-built `CodeBlock` covers most code-display needs, with optional TS IntelliSense via `fetchTypes`
- Frontend rendering requires explicit block handlers in your JSX/HTML/Markdown converter config
- See [Payload's CodeBlock source](https://github.com/payloadcms/payload/blob/main/packages/richtext-lexical/src/features/blocks/premade/CodeBlock/index.ts) for a real-world custom block example

## Sources

- `raw/rich-text__blocks.md` (https://payloadcms.com/docs/rich-text/blocks)
