This article explains the best practices for using rich text inline blocks in Payload CMS. Inline blocks allow you to insert dynamic data into content that can either be configured by users or fetched from other sources. When the value of this data changes, the content will automatically update wherever the block is used.

If you are only interested in the code, check out the sections: simple example, example with settings, configurable data instancesfrontend implementation, inline rich text changesGitHub.

Info

This article describes the usage of inline blocks that can only be used inside rich text. If you want to use them inside a single-line text field, you can use inline rich text.

Basic examples

Adding the Current Year

Let's start with a simple example: an inline block that outputs the current year dynamically. The main idea here is to avoid manually updating the year across your content every year. A common use case for this is the copyright year in the footer.

012345678import { Block } from 'payload'; export const CURRENT_YEAR: Block = { slug: 'currentYear', fields: [], }; export const INLINE_BLOCKS: Block[] = [CURRENT_YEAR];

Changes:

  1. Defined the Inline Block (1st tab)
    A CURRENT_YEAR variable was created as an inline block for rich text. This block did not require any fields. Additionally, an INLINE_BLOCKS variable was defined to group all inline blocks for export.
  2. Updated Payload Configuration (2nd tab)
    In payload.config.ts, the lexicalEditor was used, and the CURRENT_YEAR block was added to the appropriate parameter in the BlocksFeature function provided by Payload CMS.

Adding a Configurable Block: Years Since a Specific Year

Next, let’s create an inline block that calculates the number of years since a specific moment. For example, user might want to display how many years their company has been in business without manually updating the content annually.

0123456789101112131415161718192021222324252627282930313233import { Block } from 'payload'; export const CURRENT_YEAR: Block = { slug: 'currentYear', fields: [], }; export const YEARS_FROM: Block = { slug: 'yearsFrom', fields: [ { type: 'number', name: 'year', label: 'Year', validate: (value: number | null | undefined) => { const numberValue = Number(value); if (numberValue < 0) { return 'Year cannot be a negative number.'; } if (numberValue > new Date().getFullYear()) { return 'Year cannot be in the future.'; } return true; }, required: true, }, ], }; export const INLINE_BLOCKS: Block[] = [CURRENT_YEAR, YEARS_FROM];

A new block, YEARS_FROM, was added to inlineBlocks.ts.

  • This block includes a required field (year) where users input the start year.
  • Validation ensures the value is not in the future or negative.
  • In the Payload CMS admin panel, users can input the start year. The system will dynamically calculate and display the elapsed years.

Here's what it looks like in the admin panel:

"Current year" and "years from" inline blocks.
"Current year" and "years from" inline blocks.

Making Inline Blocks User-Configurable: Data Instances

To empower clients to create and manage their own dynamic data, we can allow them to configure data instances without changing the code. For example, they could add dynamic values like "Number of Clients" or "Number of Countries."

012345678910111213141516171819202122232425262728293031import type { CollectionConfig } from 'payload'; import { admins } from '@/payload/access/admins'; export const RichTextDataInstances: CollectionConfig = { slug: 'richTextDataInstances', admin: { useAsTitle: 'name', defaultColumns: ['name', 'value'], }, access: { read: () => true, update: admins, create: admins, delete: admins, }, fields: [ { type: 'text', name: 'name', label: 'Name', required: true, }, { type: 'text', name: 'value', label: 'Value', required: true, }, ], };

Steps:

  1. Created a Collection: A new collection called RichTextDataInstances was defined, allowing users to create and edit data instances.
  • This collection includes two required fields: name and value.
  • The name field uses useAsTitle to display meaningful options in the selection dropdown.
  1. Added a Dynamic Block: The inlineBlocks.ts file was updated to include a new inline blockDYNAMIC_DATA_INSTANCE.
  • This block uses a dropdown (select) to let users choose from instances created in the RichTextDataInstances collection. This field is required.
  • A description component (RichTextDataInstancesDescription) was added to guide users to the collection for creating or editing instances. Its purpose is to show the user where to create or modify rich text data instances. The source code of this component is shown on the third and fourth tabs.

Important!

Instead of using <a> or Link tags for navigation in the description, the <u> tag was used. This is because Payload CMS prevents the default click behavior for standard link tags.

  1. User Workflow
    Users can now:
  • Create dynamic data instances in the admin panel.
  • Select these instances while editing rich text content.

The process of creating a rich text instance and using it within the editor is demonstrated in the accompanying video.

Creating and usage a rich text data instance inline block.
Creating and usage a rich text data instance inline block.

Frontend implementation

The next step is to render inline blocks on the website. To do this you need to update the rich text rendering component. If you don’t already have one, you can use my example from GitHub. The changes concern the serialize function, which is called by the RichText component.

0123456789... if (_node.type === 'inlineBlock') { return ( <InlineBlock key={index} {...(node as unknown as { fields: InlineBlocksType }).fields} /> ); } ...

The first tab shows changes in serialize function. Checking for the inlineBlock node type was added. Render a custom InlineBlock component when this condition is met.

The second tab shows the source code of the InlineBlock component. It handles three different inline block types:

  • currentYear: Returns the current year using new Date().getFullYear().
  • yearsFrom: Calculates the difference between the current year and a specified starting year.
  • dynamicDataInstance: Displays the value from a selected RichTextDataInstances instance.

The result of this code is shown in the image below.

Inline blocks in rich text website appearance.
Inline blocks in rich text website appearance.

Using inline blocks in inline rich text

As mentioned earlier, you can also implement these changes for inline rich text, which is described in this article for single-line text fields. To do this, certain changes must be made to the inline rich text source code.

01234567891011121314151617181920212223242526272829303132333435363738394041424344import { BlocksFeature, lexicalEditor, LinkFeature } from '@payloadcms/richtext-lexical'; import { RichTextField } from 'payload'; import { INLINE_BLOCKS } from '@/payload/fields/inlineBlocks'; const INLINE_RICH_TEXT_FIELDS = [ 'toolbarInline', 'bold', 'italic', 'underline', 'strikethrough', 'inlineCode', 'align', ]; export const INLINE_RICH_TEXT = ( overrides?: Omit<RichTextField, 'editor' | 'type'>, ): RichTextField => { const { name = 'text', label = 'Text', admin, ...rest } = overrides ?? {}; return { type: 'richText', name, label, editor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures.filter(({ key }) => INLINE_RICH_TEXT_FIELDS.includes(key)), LinkFeature({ enabledCollections: ['pages'], }), BlocksFeature({ inlineBlocks: INLINE_BLOCKS, // Adding new feature }), ], }), admin: { ...admin, components: { Field: '@/payload/components/InlineRichText/', }, }, ...rest, }; };

In the inlineRichText.ts file, all the changes are to add a BlocksFeature with the same props as was done in payload.config.ts. The following changes have been added for the inlineRichText component:

  • "blocks" was added to the clientFeatures object, which uses the BlocksFeatureClient component provided by Payload CMS and passes the single prop "order" to it;
  • in the featureClientSchemaMap object was added "blocks" - an object describing fields for inline blocks. This value was copied from the corresponding value in common rich text using React Developer Tools.

This is how inline rich text with inline blocks looks in the admin panel:

Inline rich text with inline blocks.
Inline rich text with inline blocks.

Conclusion

In this article, we implemented rich text inline blocks and explored their functionality through examples:

  1. currentYear: A simple block to display the current year.
  2. yearsFrom: A block with a configurable year field to calculate and display elapsed time.
  3. dynamicDataInstance: A block allowing users to dynamically select and display values from a custom RichTextDataInstances collection.

Both the CMS configuration and the frontend rendering of inline blocks were covered. Additionally, it was demonstrated how these blocks can be used in inline rich text fields.

All the code from the article is available on GitHub. Feel free to use.