Blocks System
Blocks are reusable content modules that editors can add to pages in any order. This guide walks through creating a new block from scratch.
How it works
Section titled “How it works”In a typical setup, content structure and rendering are tangled in the same file. In this boilerplate they are kept separate — each block is split across two files:
| File | Role |
|---|---|
src/blocks/[name]/config.ts | Defines the fields Payload shows in the admin panel |
src/blocks/[name]/[name]-block.tsx | The React component that renders those fields on the frontend |
The config tells Payload what data to store. The component decides how to display it. They connect through RenderBlocks — a switch that maps each block’s blockType slug to its component.
Creating a New Block
Section titled “Creating a New Block”-
Create the config file
src/blocks/quote/config.ts import type { Block } from 'payload';export const quote: Block = {slug: 'quote',interfaceName: 'QuoteBlock',fields: [{name: 'content',type: 'textarea',required: true,},{name: 'author',type: 'text',},],};slugis the block’s identifier — it becomes theblockTypevalue stored in the database and used in the frontend switch.interfaceNamegives the generated TypeScript type a readable name. -
Create the component
src/blocks/quote/quote-block.tsx interface Props {content: string;author?: string;}export default function QuoteBlock({ content, author }: Props) {return (<blockquote className="quote-block"><p>"{content}"</p>{author && <cite>— {author}</cite>}</blockquote>);}The component receives the block’s fields directly as props.
authoris optional because it was not markedrequiredin the config. The&&guard prevents rendering an empty<cite>when the editor leaves it blank. -
Register in RenderBlocks
src/components/render-blocks/render-blocks.tsx import QuoteBlock from './quote/quote-block';function getBlock(block: Blocks) {switch (block.blockType) {case 'text':return <TextBlock {...block} />;case 'image':return <ImageBlock {...block} />;case 'quote':return <QuoteBlock {...block} />;default:return null;}}block.blockTypematches theslugfrom the config. Any block not listed in this switch will silently render nothing — always add a case when you create a new block. -
Add to a collection
src/collections/pages.ts import { quote } from '@/blocks/quote/config';export const pages: CollectionConfig = {fields: [{name: 'blocks',type: 'blocks',blocks: [image, text, quote],},],};Adding the config to the
blocksarray makes it available in the admin panel for that collection. Editors will see it in the block picker when editing a page.
Available Blocks
Section titled “Available Blocks”The boilerplate includes these blocks out of the box:
| Block | Purpose | Key Fields |
|---|---|---|
text | Rich text content | content (Lexical) |
image | Image with caption | image, caption |
Best Practices
Section titled “Best Practices”- Use descriptive slugs (
quotenotq) - Define
interfaceNamefor type safety - Keep components focused on rendering
- Move data transformation to hooks or utilities