Files
payload/docs/rich-text/converting-markdown.mdx
Alessio Gravili 82840aa09b refactor(richtext-lexical): new plaintext and markdown converters, restructure converter docs (#11675)
- Introduces a new lexical => plaintext converter
- Introduces a new lexical <=> markdown converter
- Restructures converter docs. Each conversion type gets its own docs
pag
2025-03-17 20:36:10 +00:00

266 lines
7.5 KiB
Plaintext

---
title: Converting Markdown
label: Converting Markdown
order: 23
desc: Converting between lexical richtext and Markdown / MDX
keywords: lexical, richtext, markdown, md, mdx
---
## Converting Richtext to Markdown
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert the lexical editor state to Markdown with the following:
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToMarkdown,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
// Your richtext data here
const data: SerializedEditorState = {}
const html = convertLexicalToMarkdown({
data,
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
})
```
### Example - outputting Markdown from the Collection
```ts
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
},
],
},
},
],
}
```
## Converting Markdown to Richtext
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert Markdown to the lexical editor state with the following:
```ts
import {
convertMarkdownToLexical,
editorConfigFactory,
} from '@payloadcms/richtext-lexical'
const html = convertMarkdownToLexical({
editorConfig: await editorConfigFactory.default({
config, // <= make sure you have access to your Payload Config
}),
markdown: '# Hello world\n\nThis is a **test**.',
})
```
## 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 `Banner` component.
- Can parse that MDX `Banner` back into a Lexical state.
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/mdx-example-light.png"
srcDark="https://payloadcms.com/images/docs/mdx-example-dark.png"
alt="Shows the Banner field in a lexical editor and the MDX output"
caption="Banner field in a lexical editor and the MDX output"
/>
```ts
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.