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.

Official Features

Below are all the Rich Text Features Payload offers. Everything is customizable; you can create your own features, modify ours and share them with the community.

Features Overview

Feature Name

Included by default

Description

BoldFeature

Yes

Adds support for bold text formatting.

ItalicFeature

Yes

Adds support for italic text formatting.

UnderlineFeature

Yes

Adds support for underlined text formatting.

StrikethroughFeature

Yes

Adds support for strikethrough text formatting.

SubscriptFeature

Yes

Adds support for subscript text formatting.

SuperscriptFeature

Yes

Adds support for superscript text formatting.

InlineCodeFeature

Yes

Adds support for inline code formatting.

ParagraphFeature

Yes

Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion.

HeadingFeature

Yes

Adds Heading Nodes (by default, H1 - H6, but that can be customized)

AlignFeature

Yes

Adds support for text alignment (left, center, right, justify)

IndentFeature

Yes

Adds support for text indentation with toolbar buttons

UnorderedListFeature

Yes

Adds support for unordered lists (ul)

OrderedListFeature

Yes

Adds support for ordered lists (ol)

ChecklistFeature

Yes

Adds support for interactive checklists

LinkFeature

Yes

Allows you to create internal and external links

RelationshipFeature

Yes

Allows you to create block-level (not inline) relationships to other documents

BlockquoteFeature

Yes

Allows you to create block-level quotes

UploadFeature

Yes

Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images

HorizontalRuleFeature

Yes

Adds support for horizontal rules / separators. Basically displays an <hr> element

InlineToolbarFeature

Yes

Provides a floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text

FixedToolbarFeature

No

Provides a persistent toolbar pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time.

BlocksFeature

No

Allows you to use Payload's Blocks Field directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field.

TreeViewFeature

No

Provides a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging

EXPERIMENTAL_TableFeature

No

Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release.

TextStateFeature

No

Allows you to store key-value attributes within TextNodes and assign them inline styles.

In depth

BoldFeature

  • Description: Adds support for bold text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Markdown Support: **bold** or __bold__
  • Keyboard Shortcut: Ctrl/Cmd + B

ItalicFeature

  • Description: Adds support for italic text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Markdown Support: *italic* or _italic_
  • Keyboard Shortcut: Ctrl/Cmd + I

UnderlineFeature

  • Description: Adds support for underlined text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Keyboard Shortcut: Ctrl/Cmd + U

StrikethroughFeature

  • Description: Adds support for strikethrough text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Markdown Support: ~~strikethrough~~

SubscriptFeature

  • Description: Adds support for subscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes

SuperscriptFeature

  • Description: Adds support for superscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes

InlineCodeFeature

  • Description: Adds support for inline code formatting with distinct styling, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Markdown Support: `code`

ParagraphFeature

  • Description: Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion.
  • Included by default: Yes

HeadingFeature

  • Description: Adds support for heading nodes (H1-H6) with toolbar dropdown and slash menu entries for each enabled heading size.
  • Included by default: Yes
  • Markdown Support: #, ##, ###, ..., at start of line.
  • Types:
1
type HeadingFeatureProps = {
2
enabledHeadingSizes?: HeadingTagType[] // ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
3
}
  • Usage example:
1
HeadingFeature({
2
enabledHeadingSizes: ['h1', 'h2', 'h3'], // Default: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
3
})

AlignFeature

  • Description: Allows text alignment (left, center, right, justify), along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Keyboard Shortcut: Ctrl/Cmd + Shift + L/E/R/J (left/center/right/justify)

IndentFeature

  • Description: Adds support for text indentation, along with buttons to apply it in both fixed and inline toolbars.
  • Included by default: Yes
  • Keyboard Shortcut: Tab (increase), Shift + Tab (decrease)
  • Types:
1
type IndentFeatureProps = {
2
/**
3
* The nodes that should not be indented. "type"
4
* property of the nodes you don't want to be indented.
5
* These can be: "paragraph", "heading", "listitem",
6
* "quote" or other indentable nodes if they exist.
7
*/
8
disabledNodes?: string[]
9
/**
10
* If true, pressing Tab in the middle of a block such
11
* as a paragraph or heading will not insert a tabNode.
12
* Instead, Tab will only be used for block-level indentation.
13
* @default false
14
*/
15
disableTabNode?: boolean
16
}
  • Usage example:
1
// Allow block-level indentation only
2
IndentFeature({
3
disableTabNode: true,
4
})

UnorderedListFeature

  • Description: Adds support for unordered lists (bullet points) with toolbar dropdown and slash menu entries.
  • Included by default: Yes
  • Markdown Support: -, *, or + at start of line

OrderedListFeature

  • Description: Adds support for ordered lists (numbered lists) with toolbar dropdown and slash menu entries.
  • Included by default: Yes
  • Markdown Support: 1. at start of line

ChecklistFeature

  • Description: Adds support for interactive checklists with toolbar dropdown and slash menu entries.
  • Included by default: Yes
  • Markdown Support: - [ ] (unchecked) or - [x] (checked)

LinkFeature

  • Description: Allows creation of internal and external links with toolbar buttons and automatic URL conversion.
  • Included by default: Yes
  • Markdown Support: [anchor](url)
  • Types:
1
type LinkFeatureServerProps = {
2
/**
3
* Disables the automatic creation of links
4
* from URLs typed or pasted into the editor,
5
* @default false
6
*/
7
disableAutoLinks?: 'creationOnly' | true
8
/**
9
* A function or array defining additional
10
* fields for the link feature.
11
* These will be displayed in the link editor drawer.
12
*/
13
fields?:
14
| ((args: {
15
config: SanitizedConfig
16
defaultFields: FieldAffectingData[]
17
}) => (Field | FieldAffectingData)[])
18
| Field[]
19
/**
20
* Sets a maximum population depth for the internal
21
* doc default field of link, regardless of the
22
* remaining depth when the field is reached.
23
*/
24
maxDepth?: number
25
} & ExclusiveLinkCollectionsProps
26
27
type ExclusiveLinkCollectionsProps =
28
| {
29
disabledCollections?: CollectionSlug[]
30
enabledCollections?: never
31
}
32
| {
33
disabledCollections?: never
34
enabledCollections?: CollectionSlug[]
35
}
  • Usage example:
1
LinkFeature({
2
fields: ({ defaultFields }) => [
3
...defaultFields,
4
{
5
name: 'rel',
6
type: 'select',
7
options: ['noopener', 'noreferrer', 'nofollow'],
8
},
9
],
10
enabledCollections: ['pages', 'posts'], // Collections for internal links
11
maxDepth: 2, // Population depth for internal links
12
disableAutoLinks: false, // Allow auto-conversion of URLs
13
})

RelationshipFeature

  • Description: Allows creation of block-level relationships to other documents with toolbar button and slash menu entry.
  • Included by default: Yes
  • Types:
1
type RelationshipFeatureProps = {
2
/**
3
* Sets a maximum population depth for this relationship,
4
* regardless of the remaining depth when the respective
5
* field is reached.
6
*/
7
maxDepth?: number
8
} & ExclusiveRelationshipFeatureProps
9
10
type ExclusiveRelationshipFeatureProps =
11
| {
12
disabledCollections?: CollectionSlug[]
13
enabledCollections?: never
14
}
15
| {
16
disabledCollections?: never
17
enabledCollections?: CollectionSlug[]
18
}
  • Usage example:
1
RelationshipFeature({
2
disabledCollections: ['users'], // Collections to exclude
3
maxDepth: 2, // Population depth for relationships
4
})

UploadFeature

  • Description: Allows creation of upload/media nodes with toolbar button and slash menu entry, supports all file types.
  • Included by default: Yes
  • Types:
1
type UploadFeatureProps = {
2
collections?: {
3
[collection: UploadCollectionSlug]: {
4
fields: Field[]
5
}
6
}
7
/**
8
* Sets a maximum population depth for this upload (not the fields for this upload), regardless of the remaining depth when the respective field is reached.
9
* This behaves exactly like the maxDepth properties of relationship and upload fields.
10
*
11
* {@link ../getting-started/concepts#field-level-max-depth}
12
*/
13
maxDepth?: number
14
} & ExclusiveUploadFeatureProps
15
16
type ExclusiveUploadFeatureProps =
17
| {
18
/**
19
* The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
20
* When this property is set, `enabledCollections` will not be available.
21
**/
22
disabledCollections?: UploadCollectionSlug[]
23
24
// Ensures that enabledCollections is not available when disabledCollections is set
25
enabledCollections?: never
26
}
27
| {
28
// Ensures that disabledCollections is not available when enabledCollections is set
29
disabledCollections?: never
30
31
/**
32
* The collections that should be enabled. Overrides the `enableRichTextRelationship` property in the collection config
33
* When this property is set, `disabledCollections` will not be available.
34
**/
35
enabledCollections?: UploadCollectionSlug[]
36
}
  • Usage example:
1
UploadFeature({
2
collections: {
3
uploads: {
4
fields: [
5
{
6
name: 'caption',
7
type: 'text',
8
label: 'Caption',
9
},
10
{
11
name: 'alt',
12
type: 'text',
13
label: 'Alt Text',
14
},
15
],
16
},
17
},
18
maxDepth: 1, // Population depth for uploads
19
disabledCollections: ['specialUploads'], // Collections to exclude
20
})

BlockquoteFeature

  • Description: Allows creation of blockquotes with toolbar button and slash menu entry.
  • Included by default: Yes
  • Markdown Support: > quote text

HorizontalRuleFeature

  • Description: Adds support for horizontal rules/separators with toolbar button and slash menu entry.
  • Included by default: Yes
  • Markdown Support: ---

InlineToolbarFeature

  • Description: Provides a floating toolbar that appears when text is selected, containing formatting options relevant to selected text.
  • Included by default: Yes

FixedToolbarFeature

  • Description: Provides a persistent toolbar pinned to the top of the editor that's always visible.
  • Included by default: No
  • Types:
1
type FixedToolbarFeatureProps = {
2
/**
3
* @default false
4
* If this is enabled, the toolbar will apply
5
* to the focused editor, not the editor with
6
* the FixedToolbarFeature.
7
*/
8
applyToFocusedEditor?: boolean
9
/**
10
* Custom configurations for toolbar groups
11
* Key is the group key (e.g. 'format', 'indent', 'align')
12
* Value is a partial ToolbarGroup object that will
13
* be merged with the default configuration
14
*/
15
customGroups?: CustomGroups
16
/**
17
* @default false
18
* If there is a parent editor with a fixed toolbar,
19
* this will disable the toolbar for this editor.
20
*/
21
disableIfParentHasFixedToolbar?: boolean
22
}
  • Usage example:
1
FixedToolbarFeature({
2
applyToFocusedEditor: false, // Apply to focused editor
3
customGroups: {
4
format: {
5
// Custom configuration for format group
6
},
7
},
8
})

BlocksFeature

  • Description: Allows use of Payload's Blocks Field directly in the editor with toolbar buttons and slash menu entries for each block type. Supports both block-level and inline blocks.
  • Included by default: No

For complete documentation including custom block components, the pre-built CodeBlock, and rendering blocks on the frontend, see the dedicated Blocks documentation.

TreeViewFeature

  • Description: Provides a debug panel below the editor showing the editor's internal state, DOM tree, and time travel debugging.
  • Included by default: No

EXPERIMENTAL_TableFeature

  • Description: Adds support for tables with toolbar button and slash menu entry for creation and editing.
  • Included by default: No

TextStateFeature

  • Description: Allows storing key-value attributes in text nodes with inline styles and toolbar dropdown for style selection.
  • Included by default: No
  • Types:
1
type TextStateFeatureProps = {
2
/**
3
* The keys of the top-level object (stateKeys) represent the attributes that the textNode can have (e.g., color).
4
* The values of the top-level object (stateValues) represent the values that the attribute can have (e.g., red, blue, etc.).
5
* Within the stateValue, you can define inline styles and labels.
6
*/
7
state: { [stateKey: string]: StateValues }
8
}
9
10
type StateValues = {
11
[stateValue: string]: {
12
css: StyleObject
13
label: string
14
}
15
}
16
17
type StyleObject = {
18
[K in keyof PropertiesHyphenFallback]?:
19
| Extract<PropertiesHyphenFallback[K], string>
20
| undefined
21
}
  • Usage example:
1
// We offer default colors that have good contrast and look good in dark and light mode.
2
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'
3
4
TextStateFeature({
5
// prettier-ignore
6
state: {
7
color: {
8
...defaultColors,
9
// fancy gradients!
10
galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
11
sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
12
},
13
// You can have both colored and underlined text at the same time.
14
// If you don't want that, you should group them within the same key.
15
// (just like I did with defaultColors and my fancy gradients)
16
underline: {
17
'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
18
// You'll probably want to use the CSS light-dark() utility.
19
'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
20
},
21
},
22
}),

This is what the example above will look like:

Example usage in light and dark mode for TextStateFeature with defaultColors and some custom styles

Rendering on the frontend

When Lexical serializes a text node that has state applied, the state is stored under a "$" key in the node object. To apply the styles when rendering rich text on your frontend, you need a custom text JSX converter that reads from this key and maps the values back to your CSS config.

Step 1 — Share your state config

Extract your TextStateFeature state into a file with no package imports so it can be safely imported in both server and client contexts:

1
// src/fields/textStateConfig.ts
2
3
export const textStateConfig = {
4
color: {
5
'text-red': {
6
label: 'Red',
7
css: {
8
color:
9
'light-dark(oklch(0.577 0.245 27.325), oklch(0.704 0.191 22.216))',
10
},
11
},
12
'text-blue': {
13
label: 'Blue',
14
css: {
15
color:
16
'light-dark(oklch(0.546 0.245 262.881), oklch(0.707 0.165 254.624))',
17
},
18
},
19
// ...other colors
20
},
21
} as const

Then reference it in your field config:

1
// src/blocks/Content/config.ts
2
3
import { TextStateFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
4
import { textStateConfig } from '@/fields/textStateConfig'
5
6
{
7
name: 'richText',
8
type: 'richText',
9
editor: lexicalEditor({
10
features: ({ rootFeatures }) => [
11
...rootFeatures,
12
TextStateFeature({
13
state: {
14
color: textStateConfig.color,
15
},
16
}),
17
],
18
}),
19
}

Step 2 — Add a custom text converter

When using RichText from @payloadcms/richtext-lexical/react, override the default text converter to read the "$" key and apply the corresponding CSS as inline styles:

1
// src/components/RichText/index.tsx
2
3
import { textStateConfig } from '@/fields/textStateConfig'
4
import {
5
JSXConvertersFunction,
6
RichText as ConvertRichText,
7
} from '@payloadcms/richtext-lexical/react'
8
9
// Lexical serializes node state under the "$" key.
10
const NODE_STATE_KEY = '$'
11
12
// React's style prop requires camelCase, but TextStateFeature CSS uses hyphen-case.
13
function hyphenToCamel(str: string): string {
14
return str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())
15
}
16
17
const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
18
...defaultConverters,
19
text: (args) => {
20
const { node } = args
21
22
// Render standard formatting (bold, italic, etc.) using the default converter
23
let text =
24
typeof defaultConverters.text === 'function'
25
? defaultConverters.text(args)
26
: node.text
27
28
// Apply TextStateFeature styles from the "$" key in the serialized node
29
const nodeState = (node as any)[NODE_STATE_KEY] as
30
| Record<string, string>
31
| undefined
32
if (nodeState) {
33
const styles: React.CSSProperties = {}
34
for (const [stateKey, stateValue] of Object.entries(nodeState)) {
35
const css = (textStateConfig as any)[stateKey]?.[stateValue]?.css
36
if (css) {
37
for (const [prop, value] of Object.entries(css)) {
38
;(styles as any)[hyphenToCamel(prop)] = value
39
}
40
}
41
}
42
if (Object.keys(styles).length > 0) {
43
text = <span style={styles}>{text}</span>
44
}
45
}
46
47
return text
48
},
49
})
50
51
export default function RichText({ data, ...rest }) {
52
return <ConvertRichText converters={jsxConverters} data={data} {...rest} />
53
}

The key insight is that textStateConfig is the single source of truth — it is passed directly to TextStateFeature in your field config and also imported by your frontend converter to resolve the CSS values at render time.

Was this page helpful?

Next

Blocks