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.

Converting HTML

Converting Rich Text to HTML

There are two main approaches to convert your Lexical-based rich text to HTML:

  1. Generate HTML on-demand (Recommended): Convert JSON to HTML wherever you need it, on-demand.
  2. Generate HTML within your Collection: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API and may not work well with live preview.

Generating HTML on-demand (Recommended)

To convert JSON to HTML on-demand, use the convertLexicalToHTML function from @payloadcms/richtext-lexical/html. Here's an example of how to use it in a React component in your frontend:

1
'use client'
2
3
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
4
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'
5
6
import React from 'react'
7
8
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
9
const html = convertLexicalToHTML({ data })
10
11
return <div dangerouslySetInnerHTML={{ __html: html }} />
12
}

Converting Lexical Blocks

If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:

1
'use client'
2
3
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
4
import type {
5
DefaultNodeTypes,
6
SerializedBlockNode,
7
SerializedInlineBlockNode,
8
} from '@payloadcms/richtext-lexical'
9
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
10
11
import {
12
convertLexicalToHTML,
13
type HTMLConvertersFunction,
14
} from '@payloadcms/richtext-lexical/html'
15
import React from 'react'
16
17
type NodeTypes =
18
| DefaultNodeTypes
19
| SerializedBlockNode<MyTextBlock>
20
| SerializedInlineBlockNode<MyInlineBlock>
21
22
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({
23
defaultConverters,
24
}) => ({
25
...defaultConverters,
26
blocks: {
27
// Each key should match your block's slug
28
myTextBlock: ({ node, providedCSSString }) =>
29
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
30
},
31
inlineBlocks: {
32
// Each key should match your inline block's slug
33
myInlineBlock: ({ node, providedStyleTag }) =>
34
`<span${providedStyleTag}>${node.fields.text}</span$>`,
35
},
36
})
37
38
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
39
const html = convertLexicalToHTML({
40
converters: htmlConverters,
41
data,
42
})
43
44
return <div dangerouslySetInnerHTML={{ __html: html }} />
45
}

Outputting HTML from the Collection

To automatically generate HTML from the saved richText field in your Collection, use the lexicalHTMLField() helper. This approach converts the JSON to HTML using an afterRead hook. For instance:

1
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
2
import type { MyTextBlock } from '@/payload-types.js'
3
import type { CollectionConfig } from 'payload'
4
5
import {
6
BlocksFeature,
7
type DefaultNodeTypes,
8
lexicalEditor,
9
lexicalHTMLField,
10
type SerializedBlockNode,
11
} from '@payloadcms/richtext-lexical'
12
13
const Pages: CollectionConfig = {
14
slug: 'pages',
15
fields: [
16
{
17
name: 'nameOfYourRichTextField',
18
type: 'richText',
19
editor: lexicalEditor(),
20
},
21
lexicalHTMLField({
22
htmlFieldName: 'nameOfYourRichTextField_html',
23
lexicalFieldName: 'nameOfYourRichTextField',
24
}),
25
{
26
name: 'customRichText',
27
type: 'richText',
28
editor: lexicalEditor({
29
features: ({ defaultFeatures }) => [
30
...defaultFeatures,
31
BlocksFeature({
32
blocks: [
33
{
34
interfaceName: 'MyTextBlock',
35
slug: 'myTextBlock',
36
fields: [
37
{
38
name: 'text',
39
type: 'text',
40
},
41
],
42
},
43
],
44
}),
45
],
46
}),
47
},
48
lexicalHTMLField({
49
htmlFieldName: 'customRichText_html',
50
lexicalFieldName: 'customRichText',
51
// can pass in additional converters or override default ones
52
converters: (({ defaultConverters }) => ({
53
...defaultConverters,
54
blocks: {
55
myTextBlock: ({ node, providedCSSString }) =>
56
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
57
},
58
})) as HTMLConvertersFunction<
59
DefaultNodeTypes | SerializedBlockNode<MyTextBlock>
60
>,
61
}),
62
],
63
}

Generating HTML in Your Frontend with Dynamic Population (Advanced)

By default, convertLexicalToHTML expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, convertLexicalToHTMLAsync, from @payloadcms/richtext-lexical/html-async. You must provide a populate function:

1
'use client'
2
3
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
4
5
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
6
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
7
import React, { useEffect, useState } from 'react'
8
9
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
10
const [html, setHTML] = useState<null | string>(null)
11
useEffect(() => {
12
async function convert() {
13
const html = await convertLexicalToHTMLAsync({
14
data,
15
populate: getRestPopulateFn({
16
apiURL: `http://localhost:3000/api`,
17
}),
18
})
19
setHTML(html)
20
}
21
22
void convert()
23
}, [data])
24
25
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
26
}

Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the getPayloadPopulateFn function:

1
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
2
3
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
4
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
5
import { getPayload } from 'payload'
6
import React from 'react'
7
8
import config from '../../config.js'
9
10
export const MyRSCComponent = async ({
11
data,
12
}: {
13
data: SerializedEditorState
14
}) => {
15
const payload = await getPayload({
16
config,
17
})
18
19
const html = await convertLexicalToHTMLAsync({
20
data,
21
populate: await getPayloadPopulateFn({
22
currentDepth: 0,
23
depth: 1,
24
payload,
25
}),
26
})
27
28
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
29
}

Converting HTML to Richtext

If you need to convert raw HTML into a Lexical editor state, use convertHTMLToLexical from @payloadcms/richtext-lexical, along with the editorConfigFactory to retrieve the editor config:

1
import {
2
convertHTMLToLexical,
3
editorConfigFactory,
4
} from '@payloadcms/richtext-lexical'
5
// Make sure you have jsdom and @types/jsdom installed
6
import { JSDOM } from 'jsdom'
7
8
const html = convertHTMLToLexical({
9
editorConfig: await editorConfigFactory.default({
10
config, // Your Payload Config
11
}),
12
html: '<p>text</p>',
13
JSDOM, // Pass in the JSDOM import; it's not bundled to keep package size small
14
})
Next

Converting Markdown