Back to Payload

Official Features

docs/rich-text/official-features.mdx

3.84.122.8 KB
Original Source

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 NameIncluded by defaultDescription
BoldFeatureYesAdds support for bold text formatting.
ItalicFeatureYesAdds support for italic text formatting.
UnderlineFeatureYesAdds support for underlined text formatting.
StrikethroughFeatureYesAdds support for strikethrough text formatting.
SubscriptFeatureYesAdds support for subscript text formatting.
SuperscriptFeatureYesAdds support for superscript text formatting.
InlineCodeFeatureYesAdds support for inline code formatting.
ParagraphFeatureYesProvides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion.
HeadingFeatureYesAdds Heading Nodes (by default, H1 - H6, but that can be customized)
AlignFeatureYesAdds support for text alignment (left, center, right, justify)
IndentFeatureYesAdds support for text indentation with toolbar buttons
UnorderedListFeatureYesAdds support for unordered lists (ul)
OrderedListFeatureYesAdds support for ordered lists (ol)
ChecklistFeatureYesAdds support for interactive checklists
LinkFeatureYesAllows you to create internal and external links
RelationshipFeatureYesAllows you to create block-level (not inline) relationships to other documents
BlockquoteFeatureYesAllows you to create block-level quotes
UploadFeatureYesAllows you to create block-level upload nodes - this supports all kinds of uploads, not just images
HorizontalRuleFeatureYesAdds support for horizontal rules / separators. Basically displays an <hr> element
InlineToolbarFeatureYesProvides a floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text
FixedToolbarFeatureNoProvides a persistent toolbar pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time.
BlocksFeatureNoAllows 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.
TreeViewFeatureNoProvides 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_TableFeatureNoAdds 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.
TextStateFeatureNoAllows 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:
ts
type HeadingFeatureProps = {
  enabledHeadingSizes?: HeadingTagType[] // ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
}
  • Usage example:
ts
HeadingFeature({
  enabledHeadingSizes: ['h1', 'h2', 'h3'], // Default: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
})

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:
ts
type IndentFeatureProps = {
  /**
   * The nodes that should not be indented. "type"
   * property of the nodes you don't want to be indented.
   * These can be: "paragraph", "heading", "listitem",
   * "quote" or other indentable nodes if they exist.
   */
  disabledNodes?: string[]
  /**
   * If true, pressing Tab in the middle of a block such
   * as a paragraph or heading will not insert a tabNode.
   * Instead, Tab will only be used for block-level indentation.
   * @default false
   */
  disableTabNode?: boolean
}
  • Usage example:
ts
// Allow block-level indentation only
IndentFeature({
  disableTabNode: true,
})

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:
ts
type LinkFeatureServerProps = {
  /**
   * Disables the automatic creation of links
   * from URLs typed or pasted into the editor,
   * @default false
   */
  disableAutoLinks?: 'creationOnly' | true
  /**
   * A function or array defining additional
   * fields for the link feature.
   * These will be displayed in the link editor drawer.
   */
  fields?:
    | ((args: {
        config: SanitizedConfig
        defaultFields: FieldAffectingData[]
      }) => (Field | FieldAffectingData)[])
    | Field[]
  /**
   * Sets a maximum population depth for the internal
   * doc default field of link, regardless of the
   * remaining depth when the field is reached.
   */
  maxDepth?: number
} & ExclusiveLinkCollectionsProps

type ExclusiveLinkCollectionsProps =
  | {
      disabledCollections?: CollectionSlug[]
      enabledCollections?: never
    }
  | {
      disabledCollections?: never
      enabledCollections?: CollectionSlug[]
    }
  • Usage example:
ts
LinkFeature({
  fields: ({ defaultFields }) => [
    ...defaultFields,
    {
      name: 'rel',
      type: 'select',
      options: ['noopener', 'noreferrer', 'nofollow'],
    },
  ],
  enabledCollections: ['pages', 'posts'], // Collections for internal links
  maxDepth: 2, // Population depth for internal links
  disableAutoLinks: false, // Allow auto-conversion of URLs
})

RelationshipFeature

  • Description: Allows creation of block-level relationships to other documents with toolbar button and slash menu entry.
  • Included by default: Yes
  • Types:
ts
type RelationshipFeatureProps = {
  /**
   * Sets a maximum population depth for this relationship,
   * regardless of the remaining depth when the respective
   * field is reached.
   */
  maxDepth?: number
} & ExclusiveRelationshipFeatureProps

type ExclusiveRelationshipFeatureProps =
  | {
      disabledCollections?: CollectionSlug[]
      enabledCollections?: never
    }
  | {
      disabledCollections?: never
      enabledCollections?: CollectionSlug[]
    }
  • Usage example:
ts
RelationshipFeature({
  disabledCollections: ['users'], // Collections to exclude
  maxDepth: 2, // Population depth for relationships
})

UploadFeature

  • Description: Allows creation of upload/media nodes with toolbar button and slash menu entry, supports all file types.
  • Included by default: Yes
  • Types:
ts
type UploadFeatureProps = {
  collections?: {
    [collection: UploadCollectionSlug]: {
      fields: Field[]
    }
  }
  /**
   * 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.
   * This behaves exactly like the maxDepth properties of relationship and upload fields.
   *
   * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
   */
  maxDepth?: number
} & ExclusiveUploadFeatureProps

type ExclusiveUploadFeatureProps =
  | {
      /**
       * The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
       * When this property is set, `enabledCollections` will not be available.
       **/
      disabledCollections?: UploadCollectionSlug[]

      // Ensures that enabledCollections is not available when disabledCollections is set
      enabledCollections?: never
    }
  | {
      // Ensures that disabledCollections is not available when enabledCollections is set
      disabledCollections?: never

      /**
       * The collections that should be enabled. Overrides the `enableRichTextRelationship` property in the collection config
       * When this property is set, `disabledCollections` will not be available.
       **/
      enabledCollections?: UploadCollectionSlug[]
    }
  • Usage example:
ts
UploadFeature({
  collections: {
    uploads: {
      fields: [
        {
          name: 'caption',
          type: 'text',
          label: 'Caption',
        },
        {
          name: 'alt',
          type: 'text',
          label: 'Alt Text',
        },
      ],
    },
  },
  maxDepth: 1, // Population depth for uploads
  disabledCollections: ['specialUploads'], // Collections to exclude
})

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:
ts
type FixedToolbarFeatureProps = {
  /**
   * @default false
   * If this is enabled, the toolbar will apply
   * to the focused editor, not the editor with
   * the FixedToolbarFeature.
   */
  applyToFocusedEditor?: boolean
  /**
   * Custom configurations for toolbar groups
   * Key is the group key (e.g. 'format', 'indent', 'align')
   * Value is a partial ToolbarGroup object that will
   * be merged with the default configuration
   */
  customGroups?: CustomGroups
  /**
   * @default false
   * If there is a parent editor with a fixed toolbar,
   * this will disable the toolbar for this editor.
   */
  disableIfParentHasFixedToolbar?: boolean
}
  • Usage example:
ts
FixedToolbarFeature({
  applyToFocusedEditor: false, // Apply to focused editor
  customGroups: {
    format: {
      // Custom configuration for format group
    },
  },
})

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:
ts
type TextStateFeatureProps = {
  /**
   * The keys of the top-level object (stateKeys) represent the attributes that the textNode can have (e.g., color).
   * The values of the top-level object (stateValues) represent the values that the attribute can have (e.g., red, blue, etc.).
   * Within the stateValue, you can define inline styles and labels.
   */
  state: { [stateKey: string]: StateValues }
}

type StateValues = {
  [stateValue: string]: {
    css: StyleObject
    label: string
  }
}

type StyleObject = {
  [K in keyof PropertiesHyphenFallback]?:
    | Extract<PropertiesHyphenFallback[K], string>
    | undefined
}
  • Usage example:
ts
// We offer default colors that have good contrast and look good in dark and light mode.
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'

TextStateFeature({
  // prettier-ignore
  state: {
    color: {
      ...defaultColors,
      // fancy gradients!
      galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
      sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
    },
    // You can have both colored and underlined text at the same time.
    // If you don't want that, you should group them within the same key.
    // (just like I did with defaultColors and my fancy gradients)
    underline: {
      'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
       // You'll probably want to use the CSS light-dark() utility.
      'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
    },
  },
}),

This is what the example above will look like:

<LightDarkImage srcDark="https://payloadcms.com/images/docs/text-state-feature.png" srcLight="https://payloadcms.com/images/docs/text-state-feature.png" alt="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:

ts
// src/fields/textStateConfig.ts

export const textStateConfig = {
  color: {
    'text-red': {
      label: 'Red',
      css: {
        color:
          'light-dark(oklch(0.577 0.245 27.325), oklch(0.704 0.191 22.216))',
      },
    },
    'text-blue': {
      label: 'Blue',
      css: {
        color:
          'light-dark(oklch(0.546 0.245 262.881), oklch(0.707 0.165 254.624))',
      },
    },
    // ...other colors
  },
} as const

Then reference it in your field config:

ts
// src/blocks/Content/config.ts

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

{
  name: 'richText',
  type: 'richText',
  editor: lexicalEditor({
    features: ({ rootFeatures }) => [
      ...rootFeatures,
      TextStateFeature({
        state: {
          color: textStateConfig.color,
        },
      }),
    ],
  }),
}

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:

tsx
// src/components/RichText/index.tsx

import { textStateConfig } from '@/fields/textStateConfig'
import {
  JSXConvertersFunction,
  RichText as ConvertRichText,
} from '@payloadcms/richtext-lexical/react'

// Lexical serializes node state under the "$" key.
const NODE_STATE_KEY = '$'

// React's style prop requires camelCase, but TextStateFeature CSS uses hyphen-case.
function hyphenToCamel(str: string): string {
  return str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())
}

const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
  ...defaultConverters,
  text: (args) => {
    const { node } = args

    // Render standard formatting (bold, italic, etc.) using the default converter
    let text =
      typeof defaultConverters.text === 'function'
        ? defaultConverters.text(args)
        : node.text

    // Apply TextStateFeature styles from the "$" key in the serialized node
    const nodeState = (node as any)[NODE_STATE_KEY] as
      | Record<string, string>
      | undefined
    if (nodeState) {
      const styles: React.CSSProperties = {}
      for (const [stateKey, stateValue] of Object.entries(nodeState)) {
        const css = (textStateConfig as any)[stateKey]?.[stateValue]?.css
        if (css) {
          for (const [prop, value] of Object.entries(css)) {
            ;(styles as any)[hyphenToCamel(prop)] = value
          }
        }
      }
      if (Object.keys(styles).length > 0) {
        text = <span style={styles}>{text}</span>
      }
    }

    return text
  },
})

export default function RichText({ data, ...rest }) {
  return <ConvertRichText converters={jsxConverters} data={data} {...rest} />
}

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.