Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.
AuthorNick Vogel

How to build flexible layouts with Payload blocks

Community Guide
AuthorNick Vogel

Learn how to set it up, configure it, and use the Payload blocks field to build dynamic page layouts—perfect for blogs, landing pages, and customizable components.

The Blocks field in Payload is used to store an array of objects that you define elsewhere to be reused anywhere. Each Blocks field has its own unique schema, which you set.

You can use Blocks to create a flexible content model. Blocks allow you to create highly customizable pages and post layouts using a single field across documents instead of having preset groupings of fields set at the collection level—this grants you the flexibility you need to create pages that follow similar templates but allow for different layouts.

While you can create Blocks in any collection, it’s best practice to create a separate directory called "blocks" and create a new file named after the block you want to create.

Since Next.js and Payload are now fully integrated, you can even include the component files this directory to keep everything together. After creating a block, you can then import it into the collection you’d like to enable blocks in by adding a blocks field with some configuration options.

Getting started with Payload blocks

To get started, we're going to create a new "blocks" directory within our src directory. In this new folder, we'll create a new TypeScript file named contentWithMedia.ts, and at the top of the file you’ll import your block from Payload.

1
import {Block} from 'payload'

Then you’ll export a new const called ContentWithMedia, and assign it the Block type. Set it equal to an empty object, and then we’re ready to set configuration options.

1
import {Block} from 'payload'
2
3
export const ContentWithMedia: Block = {
4
5
}

First, we need to set the slug. This is how we'll be able to identify it in our frontend when we query a Collection.

You can change how this block’s title is displayed on the Payload dashboard by setting your labels property, using an object that contains an option for both singular and plural versions of your label.

1
import {Block} from 'payload'
2
3
export const ContentWithMedia: Block = {
4
slug: 'ContentWithMedia',
5
labels: {
6
singular: 'Content with Media Block'
7
plural: 'Content with Media Blocks'
8
}
9
}

With the slug set, the only other required option is fields. We’ll keep this simple by only including a RichText field, an image field, and a radio button that allows us to toggle between text on the left or right:

1
import {Block} from 'payload'
2
3
export const ContentWithMedia: Block = {
4
slug: 'ContentWithMedia',
5
labels: {
6
singular: 'Content with Media Block'
7
plural: 'Content with Media Blocks'
8
},
9
fields: [
10
{
11
type: 'richText',
12
name: 'content',
13
},
14
{
15
type: 'upload',
16
name: 'image',
17
relationTo: 'media',
18
},
19
{
20
type: 'radio',
21
name: 'textPosition',
22
options: ['Left', 'Right']
23
},
24
]
25
}

You may also want to include other fields like a header section or a field to set an anchor ID.

Adding a block field to your collection

We can then import this block into our Collection config of choice as a field, and we’ll see the ability to add a block added to the dashboard.

So we'll navigate to our Posts config, go to the fields section, and add a new field:

1
fields: [
2
{
3
type: 'blocks',
4
blocks: [
5
ContentWithMedia
6
],
7
name: 'blockTest',
8
},
9
{
10
name: 'title',
11
type: 'text',
12
},
13
{
14
name: 'slug',
15
type: 'text',
16
},
17
]

You can use this now to set the order of how you want things to render on the frontend.

So now if you navigate to the Collection in the admin panel, we can see the option to add a block. When this is clicked, a drawer opens up with a Content with Media block option.

You then have the ability to add content, to add an image, and to set the text position left and right. You can also click the API tab to see what the API response looks like when multiple blocks are added.

Aside from the required fields, there are other, unique options available to you for Block fields. There are three sets of config options for Blocks: field, admin, and block configs.

As a field, blocks have many of the same configuration options available as other fields. The name and blocks options are, of course, required, but you’re also able to change how the block’s name is displayed by using label. For example, you can disable the label by setting label to false.

Setting minRows and maxRows

Since the blocks field is like an array field, you can use minRows and maxRows to restrict how many blocks can be included in any collections block field.

1
fields: [
2
{
3
type: 'blocks',
4
blocks: [
5
ContentWithMedia,
6
],
7
name: 'blockTest',
8
label: false,
9
minRows: 1,
10
maxRows: 20, // Now, no more than 20 blocks can be included in this field
11
}

Access control with Payload blocks

Like most other fields, you can set fine-tuned access controls to the field to control who can create, read, update, or delete blocks or operate this field in other ways.

You can also keep the field hidden from the API in the admin panel by setting hidden to true.

For admin configurations, you are able to set initCollapsed and isSortable to true or false:

1
fields: [
2
{
3
type: 'blocks',
4
admin: {
5
initCollapsed: true, // By default, this is false, so everything would be shown on page load
6
isSortable: false, // By default, this is true, so you'll have the ability to click and drag and sort any of your blocks
7
},
8
blocks: [
9
ContentWithMedia
10
],
11
name: 'blockTest',
12
label: false,
13
minRows: 1,
14
maxRows: 20,
15
}
16
]

Unique block configurations

Inside the block config you created, you’re able to set labels for how the block is labeled in your admin dashboard as we've seen. If you don’t provide values here, it’s automatically generated from the slug you provide.

You can also change the default image that’s used in the Blocks drawer by setting imageURL to a URL of an image you’d like to use:

1
import { Block } from 'payload'
2
3
export const ContentWithMedia: Block = {
4
slug: 'contentWithMedia',
5
labels: {
6
singular: 'Content with Media Block',
7
plural: 'Content with Media Blocks',
8
},
9
imageURL: 'https://google.com/path/to/image', // This would replace the default image
10
imageAltText: 'Something describing the text', // This can help with accessibility
11
fields: [
12
{
13
type: 'richText',
14
name: 'content',
15
},
16
{
17
type: 'upload',
18
name: 'image',
19
relationTo: 'media',
20
},
21
],
22
}

Block type is saved as the slug of the block, allowing you to map through your blocks array to render data on the frontend, and name adds an option to the admin panel that allows editors to edit the label of their blocks for better readability.

Final thoughts

Blocks are an incredibly powerful and flexible tool that allows you to create customizable layouts using Blocks that you define. You have many options available to you at the field, admin, and block config levels, so you can truly create these blocks to be anything you want them to be.