8.7 KiB
| title | label | order | desc | keywords | source |
|---|---|---|---|---|---|
| Converting Markdown | Converting Markdown | 23 | Converting between lexical richtext and Markdown / MDX | lexical, richtext, markdown, md, mdx | https://payloadcms.com/docs/rich-text/converting-markdown |
Richtext to Markdown
If you have access to the Payload Config and the lexical editor config, you can convert the lexical editor state to Markdown with the following:
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToMarkdown,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
// Your richtext data here
const data: SerializedEditorState = {}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
})
Example - outputting Markdown from the Collection
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import type { CollectionConfig, RichTextField } from 'payload'
import {
convertLexicalToMarkdown,
editorConfigFactory,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor(),
},
{
name: 'markdown',
type: 'textarea',
admin: {
hidden: true,
},
hooks: {
afterRead: [
({ siblingData, siblingFields }) => {
const data: SerializedEditorState =
siblingData['nameOfYourRichTextField']
if (!data) {
return ''
}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: editorConfigFactory.fromField({
field: siblingFields.find(
(field) =>
'name' in field && field.name === 'nameOfYourRichTextField',
) as RichTextField,
}),
})
return markdown
},
],
beforeChange: [
({ siblingData }) => {
// Ensure that the markdown field is not saved in the database
delete siblingData['markdown']
return null
},
],
},
},
],
}
Markdown to Richtext
If you have access to the Payload Config and the lexical editor config, you can convert Markdown to the lexical editor state with the following:
import {
convertMarkdownToLexical,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
const lexicalJSON = convertMarkdownToLexical({
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
markdown: '# Hello world\n\nThis is a **test**.',
})
Converting Uploads
When converting Markdown to Lexical, standard image syntax ![alt] (url) is not automatically converted to Upload nodes, because Payload has no way to look up a Media document from a URL alone.
The UploadFeature includes a built-in transformer that recognizes a special placeholder format:
![media:6507f7b9a4d3c2e1f0ab1234]()
Where media is your upload collection slug and the second part is the document ID. When convertMarkdownToLexical processes this, it creates a proper Upload node with the correct relationTo and value.
When converting from Lexical to Markdown, Upload nodes are serialized back into this same placeholder format (unless the document is populated, in which case the URL and alt text are used directly as a standard markdown image).
Migrating existing Markdown content
If you're migrating content that contains standard ![alt] (url) image references, the recommended approach is:
- Upload your images to the Media collection first to get their document IDs
- Pre-process your Markdown to replace
![alt] (url)with![media:<id>]() - Then run
convertMarkdownToLexical
Converting MDX
Payload supports serializing and deserializing MDX content. While Markdown converters are stored on the features, MDX converters are stored on the blocks that you pass to the BlocksFeature.
Defining a Custom Block
Here is an example of a Banner block.
This block:
- Renders in the admin UI as a normal Lexical block with specific fields (e.g. type, content).
- Converts to an MDX
Bannercomponent. - Can parse that MDX
Bannerback into a Lexical state.
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import type { Block, CollectionConfig, RichTextField } from 'payload'
import {
BlocksFeature,
convertLexicalToMarkdown,
editorConfigFactory,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
const BannerBlock: Block = {
slug: 'Banner',
fields: [
{
name: 'type',
type: 'select',
defaultValue: 'info',
options: [
{ label: 'Info', value: 'info' },
{ label: 'Warning', value: 'warning' },
{ label: 'Error', value: 'error' },
],
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor(),
},
],
jsx: {
/**
* Convert from Lexical -> MDX:
* <Banner type="..." >child content</Banner>
*/
export: ({ fields, lexicalToMarkdown }) => {
const props: any = {}
if (fields.type) {
props.type = fields.type
}
return {
children: lexicalToMarkdown({ editorState: fields.content }),
props,
}
},
/**
* Convert from MDX -> Lexical:
*/
import: ({ children, markdownToLexical, props }) => {
return {
type: props?.type,
content: markdownToLexical({ markdown: children }),
}
},
},
}
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [BannerBlock],
}),
],
}),
},
{
name: 'markdown',
type: 'textarea',
hooks: {
afterRead: [
({ siblingData, siblingFields }) => {
const data: SerializedEditorState =
siblingData['nameOfYourRichTextField']
if (!data) {
return ''
}
const markdown = convertLexicalToMarkdown({
data,
editorConfig: editorConfigFactory.fromField({
field: siblingFields.find(
(field) =>
'name' in field && field.name === 'nameOfYourRichTextField',
) as RichTextField,
}),
})
return markdown
},
],
beforeChange: [
({ siblingData }) => {
// Ensure that the markdown field is not saved in the database
delete siblingData['markdown']
return null
},
],
},
},
],
}
The conversion is done using the jsx property of the block. The export function is called when converting from lexical to MDX, and the import function is called when converting from MDX to lexical.
Export
The export function takes the block field data and the lexicalToMarkdown function as arguments. It returns the following object:
| Property | Type | Description |
|---|---|---|
children |
string | This will be in between the opening and closing tags of the block. |
props |
object | This will be in the opening tag of the block. |
Import
The import function provides data extracted from the MDX. It takes the following arguments:
| Argument | Type | Description |
|---|---|---|
children |
string | This will be the text between the opening and closing tags of the block. |
props |
object | These are the props passed to the block, parsed from the opening tag into an object. |
The returning object is equal to the block field data.