Trendy Coder Logo
  • Home
  • About
  • Blog
  • Newsletter

Maximizing Efficiency: The Power of Payload CMS Blocks

Posted on: August 11 2025
By Dave Becker
Hero image for Maximizing Efficiency: The Power of Payload CMS Blocks

One of the primary benefits of a Headless CMS solution is the ability to create manageable content blocks for fast content delivery and updates. Enter Payload CMS content blocks — a game-changing feature that helps streamline the way content is structured and delivered on websites and applications.

Payload CMS supports flexible and modular blocks to allow designers, content managers and developers to create reusable content.

In this article I'll cover the following:

  • What are Payload content block types?
  • Benefits of using Payload blocks.
  • How to build a content block.
  • Querying content block data.
  • How to style Payload content blocks.

Whether you’re new to Headless CMS or have some experience, understanding the power of these blocks will be a game-changer.

Let's get started and learn how to use them in a creative and meaningful way.

Getting Started

If you would like to follow along, you can generate a Payload project with the following command.

npx create-payload-app

What is a Payload Content Block?

Payload provides a special built-in field type called blocks for creating resuable and modular blocks of content.

This is really at the heart of what makes Payload a powerful framework and Headless CMS system.

By using the blocks field type in your Collection, you can create an array of custom blocks.

  {
    name: 'layouts', 
    type: 'blocks',
    blocks: [
      // array of blocks here
    ]
  }

Each block can have any number of the other built-in field types within it to define the needed structure for your data.

Like the other built-in field types, Payload will automicatally generate the corresponding Admin UI form for managing and editing content blocks.

Having this capability in Payload CMS makes it a great tool for building manageable content authoring solutions.

Collections with block content

To get a better idea of what blocks actually are, here's an illustration. In the design concept below, you'll see two vertically stacked blocks.

  • Call To Action
  • Carousel

Each is highlghted with a red border to make it easier to see.

There are only two content blocks in this design example but you could create any number of blocks for all types of purposes and audiences.

Shows a visual web design concept, with payload block content stacked vertically.

Benefits of layout blocks

Payload uses the concept of blocks to create page layout builders, which include the following benefits:

  • Easy for content managers to dynamically build and maintain pages.
  • Each block has its own schema and typing.
  • Blocks are flexible and reusable.
  • Blocks can be used in plugins.

In my own experience, blocks are an excellent choice for building rich marketing content. When used with a team or on a project the benefits include:

  • Better collaboration for designers, content authors and developers.
  • Simplifies rollout of new content block creation for content authors.
  • Developers can focus exclusively on new content block design implementation, rather than maintaning a complex code base to do the same.

Building Payload blocks

For this example, I'll use a simple pages Collection and add the blocks under the field section. I'll build two content blocks:

  • Banner: Displays rich text and CTA link overlayed on top of a background image.
  • Content: Simple rich text content block.
/src/payload/collections/Pages.ts
import type { CollectionConfig } from 'payload'
import Banner from '../../blocks/Banner/config'
import Content from '../../blocks/Content/config'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    useAsTitle: 'title',
  },
  access: {
    read: () => true
  },
  fields: [
    {
      name: 'slug',
      label: 'Slug',
      type: 'text',
      index: true,
      admin: {
        position: 'sidebar',
      },
    },
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'description',
      type: 'text',
    },
    {
      name: 'layouts',
      type: 'blocks',
      required: false,
      blocks: [Banner, Content],
    },
  ],
}

The pages Collection contains the following fields:

  • slug: Used for the page url path and SEO optimization
  • title: General title but can also be used for SEO.
  • description: General description but should be supplied for SEO.
  • layouts: An array of blocks to hold all of the content blocks.

Defining Payload content blocks

Having a consistent way of creating blocks, makes things easier to manage over time.

Here's a simple directory pattern that you can use to get started.

Create a /payload folder, with two subdirectories, blocks and components.

These directories have the following benefits:

  • blocks: Each block is directly under the blocks directory and has a config.ts, so the configuration is co-located next to the actual UI component.
  • components: A folder to place all of the React UI components that your Payload content blocks will use to render HTML.

Note: Try to maximize server side rendering for 'blocks' and 'components' by separating component parts into server and client components using 'use client' supported by Next.js.

The two blocks that I'll add will have the following format:

 /src/payload/
  ├── blocks
  │   ├── Banner
  │   │   ├── config.ts
  │   │   ├── Component.tsx
  │   │   └── index.ts // an index to export any UI
  │   └── Content
  │       ├── config.ts
  │       ├── Component.tsx
  │       └── index.ts // an index to export any UI
  └── components

Banner block configuration

Let's break down the Banner block. It has the following fields added:

  • media: A standard media upload block to hold the background image for the block.
  • richText: Any rich text that will be overlayed on top of the background image.
  • links: An array of CTA links that will be overlayed and listed to call the attention of the user. Each link holds the display text, url and a checkbox to open the content in a new window.

Let's take a look at the typing that Payload provides, which includes:

  • Block type for blocks.
  • Optional interfaceName key such as BannerBlock

Payload will generate a type with the interfaceName, which can also be used as the props type base for React components.

/src/payload/blocks/Banner/config.ts
import type { Block } from 'payload'

import { lexicalEditor } from '@payloadcms/richtext-lexical'

const Banner: Block = {
  slug: 'banner',
  interfaceName: 'BannerBlock',
  fields: [
    {
      name: 'media',
      label: 'Background Image',
      type: 'upload',
      relationTo: 'media',
      required: true,
    },
    {
      name: 'richText',
      type: 'richText',
      editor: lexicalEditor(),
      label: 'Content',
    },
    {
      name: 'links',
      interfaceName: 'LinkGroupType',
      label: 'Links',
      type: 'array',
      fields: [
        {
          name: 'link',
          interfaceName: 'LinkType',
          type: 'group',
          admin: {
            hideGutter: true,
          },
          fields: [
            {
              name: 'displayText',
              label: 'Link Text',
              type: 'text',
              required: true
            },
            {
              type: 'row',
              fields: [
                {
                  name: 'type',
                  type: 'radio',
                  admin: {
                    layout: 'horizontal',
                    width: '50%',
                  },
                  defaultValue: 'reference',
                  options: [
                    {
                      label: 'Internal link',
                      value: 'reference',
                    },
                    {
                      label: 'Custom URL',
                      value: 'custom',
                    },
                  ],
                },
                {
                  name: 'reference',
                  type: 'relationship',
                  admin: {
                    condition: (_, siblingData) => siblingData?.type === 'reference',
                  },
                  label: 'Document to link to',
                  relationTo: ['pages'],
                  required: true,
                },
                {
                  name: 'url',
                  type: 'text',
                  admin: {
                    condition: (_, siblingData) => siblingData?.type === 'custom',
                  },
                  label: 'Custom URL',
                  required: true,
                },
                
                {
                  name: 'newTab',
                  type: 'checkbox',
                  admin: {
                    style: {
                      alignSelf: 'flex-end',
                    },
                    width: '50%',
                  },
                  label: 'Open in new tab',
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}

export default Banner

The admin section is using a conditional check on the type field value, to toggle which field type to show based on user selection.

This is a powerful feature in Payload and can be used to check any sibling data in your Collection or Block.

admin: {
  condition: (_, siblingData) => siblingData?.type === 'reference',
},

Content block configuration

Let's break down the Content block. It only has a single field added:

  • richText: Holds any rich text content added through the lexical defined.
/src/payload/blocks/Content/config.ts
import type { Block } from 'payload'

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


const Content: Block = {
  slug: 'content',
  interfaceName: 'ContentBlock',
  fields: [
    {
      name: 'richText',
      type: 'richText',
      editor: lexicalEditor(),
      label: false,
    },
  ],
}

export default Content;

Adding Payload content blocks

At this stage all you need to do is add the Pages Collection to the main payload.config.ts file and start up the server.

The Users and Media Collections are already defined when you generate a new Payload project with the npx create-payload-app command.

import { Users } from './payload/collections/Users'
import { Media } from './payload/collections/Media'
import { Pages } from './payload/collections/Pages'

export default buildConfig({
  // ...
  collections: [Users, Media, Pages],
  // ...
})

After starting up the dev server, head over to the Admin UI section and locate the Pages section.

Click on the Create New button and you'll see the following in the Admin UI.

I've provided some details for the title, description and slug fields.

Payload admin UI showing a create new page Collection document.

Notice the Add Layout link at the bottom. This is where you can start adding blocks.

By clicking on the Add Layout link, you'll see the following blocks we defined in the blocks field listed.

Payload CMS admin UI showing selection menu to select a block content type.

Adding a Banner layout block will show the following screen.

Payload CMS Collection Admin UI form after selecting a banner content type and shows a media upload form element to select the background image.

For this demo I'll select and upload a new background image and set some rich text for the overlay.

I'll also add a single CTA link.

Shows a Payload CMS block layout Banner field type with a selected image,sample rich text and one CTA link.

Next I'll add another layout block for the Content block. I'll provide some simple rich text as follows.

Shows a Payload CMS block layout Content field type with a lexical rich text editor

Make sure to Save all of the details added so far.

At this point the data is now saved in the database and ready to be consumed.

Querying and accessing Collection data

Now that the data is saved, it's ready to be queried using Payloads API's.

I will be using the REST API for this example but as your content blocks become more complex and even nested, you might want to use GraphQL for more control over the query and response.

For a more in-depth article on using GraphQL GraphQL Optimization in Payload CMS

Query by slug is better for SEO

Since the page is now identified with a slug, we can query on the slug field versus the ID which is good SEO practice.

Remember, the pages Collection security access is set to public read for now, just be sure to adjust the access control to your requirements before deploying to a public server.

Run the following query in the browser window.

http://localhost:3000/api/pages?[where][slug][equals]=block-demo

Response:

{
    "docs": [
        {
            "id": 1,
            "slug": "block-demo",
            "title": "Block Demo",
            "description": "Here's a demo page showing Payload block content",
            "layouts": [
                {
                    "id": "67d5cc675a144daf52e5b7ea",
                    "media": {
                        "id": 4,
                        "alt": "Country roads",
                        "updatedAt": "2025-03-15T00:56:35.026Z",
                        "createdAt": "2025-03-15T00:56:35.026Z",
                        "url": "/api/media/file/country-roads.png",
                        "thumbnailURL": null,
                        "filename": "country-roads.png",
                        "mimeType": "image/png",
                        "filesize": 5734340,
                        "width": 2228,
                        "height": 1192,
                        "focalX": 50,
                        "focalY": 50
                    },
                    "richText": {
                        "root": {
                            "children": [
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Build Block Content that Engages Your Customers",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "center",
                                    "indent": 0,
                                    "type": "heading",
                                    "version": 1,
                                    "tag": "h1"
                                },
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Your content blocks should engage your customers and should be user friendly.",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "center",
                                    "indent": 0,
                                    "type": "quote",
                                    "version": 1
                                }
                            ],
                            "direction": "ltr",
                            "format": "",
                            "indent": 0,
                            "type": "root",
                            "version": 1
                        }
                    },
                    "blockName": null,
                    "links": [
                        {
                            "id": "67d5f2f86dc32bbf52e01293",
                            "link": {
                                "displayText": "Get Started",
                                "type": "custom",
                                "url": "/block-demo",
                                "newTab": null
                            }
                        }
                    ],
                    "blockType": "banner"
                },
                {
                    "id": "67d5f3146dc32bbf52e01295",
                    "richText": {
                        "root": {
                            "children": [
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Display Elegant Rich Text Content",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "",
                                    "indent": 0,
                                    "type": "heading",
                                    "version": 1,
                                    "tag": "h1"
                                },
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "",
                                    "indent": 0,
                                    "type": "paragraph",
                                    "version": 1,
                                    "textFormat": 0,
                                    "textStyle": ""
                                },
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "",
                                    "indent": 0,
                                    "type": "paragraph",
                                    "version": 1,
                                    "textFormat": 0,
                                    "textStyle": ""
                                },
                                {
                                    "children": [
                                        {
                                            "detail": 0,
                                            "format": 0,
                                            "mode": "normal",
                                            "style": "",
                                            "text": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?",
                                            "type": "text",
                                            "version": 1
                                        }
                                    ],
                                    "direction": "ltr",
                                    "format": "",
                                    "indent": 0,
                                    "type": "paragraph",
                                    "version": 1,
                                    "textFormat": 0,
                                    "textStyle": ""
                                }
                            ],
                            "direction": "ltr",
                            "format": "",
                            "indent": 0,
                            "type": "root",
                            "version": 1
                        }
                    },
                    "blockName": null,
                    "blockType": "content"
                }
            ],
            "updatedAt": "2025-03-15T21:37:45.999Z",
            "createdAt": "2025-03-13T21:26:20.462Z"
        }
    ],
    "hasNextPage": false,
    "hasPrevPage": false,
    "limit": 10,
    "nextPage": null,
    "page": 1,
    "pagingCounter": 1,
    "prevPage": null,
    "totalDocs": 1,
    "totalPages": 1
}

Rendering blocks with Next.js pages

In order to render this data in a meaningful way, we'll need a Next.js page and some React UI components.

I'll also use TailwindCSS to add some styling.

Now that Payload is fully Next.js native, the app directory is separated using route groups in Next.js using the (...) notation.

I'll add a [slug] to catch all of the path params for the page routes.

 /src/app/
  ├── (frontend)
  │   └── [slug]
  │       └── page.tsx
  └── (payload)

Here's a simple example of the Next.js page which will be fully rendered on the server side for this example.

/src/app/(frontend)/[slug]/page.tsx
import React from 'react'

import { Page as PageType } from '@payload-types'
import { Blocks } from '@/payload/components/Blocks'

type Args = {
  params: Promise<{
    slug?: string
  }>
}

export default async function Page({ params: paramsPromise }: Args) {
  const { slug } = await paramsPromise

  const res = await fetch(`http://localhost:3000/api/pages?[where][slug][equals]=${slug}`, {
    method: 'GET',
  })
  const data = await res.json()
  const page: PageType = data?.docs?.[0]

  const { layouts } = page

  return <article className="">{layouts && layouts.length && <Blocks blocks={layout} />}</article>
}

Rendering an array of blocks

Since the layouts field in the pages Collection is an array, we'll need a way to loop through and render the Payload content blocks.

/src/payload/components/Blocks/index.tsx
import React, { Fragment } from 'react'

import { Page } from '@payload-types'
import { Banner } from '../../blocks/Banner'
import { Content } from '../../blocks/Content'

const componentsMapping = {
  'banner': Banner,
  'content': Content,
}

export const Blocks: React.FC<{
  blocks?: Page['layouts']
}> = (props) => {
  const { blocks } = props

  const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0

  if (hasBlocks) {
    return (
      <React.Fragment>
        {blocks.map((block, index) => {
          const blockType = block.blockType
          const blockName = block.blockName || `${index}`

          if (blockType && componentsMapping[blockType]) {
            const Block: any = componentsMapping[blockType]
            
            return (
              <div key={`${blockType}-${index}`} id={`${blockType}-${blockName}`} className="min-h-[400px]">
                <Block {...block} />
              </div>
            )
          }
          return null
        })}
      </React.Fragment>
    )
  }

  return null
}

Each block will need a React UI component to actually render the content block data.

Rendering the Banner block

Here's the Banner content block UI component.

/src/payload/blocks/Banner/Component.tsx
import React from 'react'
import { BannerBlock } from '@payload-types'
import RichText from '@/payload/components/RichText'
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

type BannerProps = BannerBlock

export const getClientSideURL = () => {
  return process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'
}

export const Banner: React.FC<BannerProps> = ({ media, richText, links }) => {
  let alt: string = ''
  let src: string = ''
  if (media && typeof media === 'object') {
    const { alt: mediaAlt, url } = media

    alt = mediaAlt || ''
    src = `${getClientSideURL()}${url}`
  }

  return (
    <div className="relative h-full">
      <img alt={alt} className="absolute inline h-full w-full aspect-video -z-50" src={src} />
      <div className="absolute opacity-50 h-full w-full bg-gray-600 -z-20"></div>
      <div className="relative mx-auto px-4 py-20 flex justify-center">
        <div className="mx-auto max-w-xl text-center text-white!">
          <RichText data={richText as SerializedEditorState} />

          <div className="mt-8 flex flex-wrap justify-center gap-4">
            {links &&
              links.map(({ link: { url, displayText } }) => {
                return (
                  <a
                    className="block w-full rounded-sm bg-green-600 px-12 py-3 font-medium text-white shadow-sm hover:bg-green-700 focus:ring-3 focus:outline-hidden sm:w-auto"
                    href={url as string}
                  >
                    {displayText}
                  </a>
                )
              })}
          </div>
        </div>
      </div>
    </div>
  )
}

Rendering the Content block

And here's the Content block UI component.

/src/payload/blocks/Content/Component.tsx
import React from 'react'
import RichText from '../../components/RichText'
import { ContentBlock } from '@payload-types'

type ContentProps = ContentBlock
export const Content: React.FC<ContentProps> = (props) => {
  const { richText: content } = props

  return (
    <div className="flex p-10">
      <div>{content && <RichText data={content} className="prose" />}</div>
    </div>
  )
}

Rendering rich text content

And lastly, here's a helper component for displaying rich text content.

This is a pretty basic rich text component but it can always be enhanced.

/src/payload/components/RichText/index.tsx
import React from 'react';

import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';

import {
  RichText as RichTextWithoutBlocks,
} from '@payloadcms/richtext-lexical/react';

type Props = {
  data: SerializedEditorState;
  className?: string;
} & React.HTMLAttributes<HTMLDivElement>;

export default function RichText(props: Props) {
  const { data, className = '' } = props;
  return (
    <RichTextWithoutBlocks
      className={className}
      data={data}
    />
  );
}

Viewing Payload content blocks in the browser

Here's the result of the example as it would be rendered in the actual browser.

http://localhost:3000/block-demo

As you can see, using blocks is a very powerful way to create reusable content blocks without the complexity.

Shows a web page with two Payload layout blocks that have been fully rendered as HTML using React components.

In Conclusion

Most marketing companies are switching to block level content authoring solutions.

Using Payload Headless CMS is a great way to achieve those goals in a very short period of time. Payload provides all of the support to build dynamically managed page content.

Despite the learning curve that comes with any new technology, using Payload has streamlined the process of building block content.

I hope that you found value in this article and that it might help you on your next development project. Cheers!

Topics

SEOLinuxSecuritySSHEmail MarketingMore posts...

Related Posts

Hero image for Boost Payload CMS with Search: Step-by-Step Tutorial
Posted on: August 11 2025
By Dave Becker
Boost Payload CMS with Search: Step-by-Step Tutorial
Hero image for Server-Side Pagination Made Easy in Payload CMS
Posted on: August 11 2025
By Dave Becker
Server-Side Pagination Made Easy in Payload CMS
Hero image for Payload CMS: Getting Started Using the New Join Field
Posted on: August 11 2025
By Dave Becker
Payload CMS: Getting Started Using the New Join Field
Hero image for Maximizing Efficiency: The Power of Payload CMS Blocks
Posted on: August 11 2025
By Dave Becker
Maximizing Efficiency: The Power of Payload CMS Blocks
Hero image for Create Custom Forms Using Payload CMS Form Builder Plugin
Posted on: August 11 2025
By Dave Becker
Create Custom Forms Using Payload CMS Form Builder Plugin
Hero image for Payload CMS SEO Plugin: Boosting Your Site's Search Ranking
Posted on: April 04 2025
By Dave Becker
Payload CMS SEO Plugin: Boosting Your Site's Search Ranking
Hero image for GraphQL Optimization in Payload CMS
Posted on: April 04 2025
By Dave Becker
GraphQL Optimization in Payload CMS
Hero image for Exploring the Game-Changing Features of Payload CMS 3.0
Posted on: April 04 2025
By Dave Becker
Exploring the Game-Changing Features of Payload CMS 3.0
Hero image for Document Nesting With Payload's Nested Docs Plugin
Posted on: April 04 2025
By Dave Becker
Document Nesting With Payload's Nested Docs Plugin
Hero image for Payload CMS Collections: How They Streamline Content Management
Posted on: April 04 2025
By Dave Becker
Payload CMS Collections: How They Streamline Content Management
Trendy Coder Logo
Resources
  • Blog
Website
  • Home
  • About us
Subscribe

Get the latest news and articles to your inbox periodically.

We respect your email privacy

© TrendyCoder.com. All rights reserved.