# Converting Markdown

Source: https://payloadcms.com/docs/rich-text/converting-markdown

## Richtext to Markdown

If you have access to the Payload Config and the [lexical editor config](/docs/v3/rich-text/converters.md#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 markdown = 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
          },
        ],
      },
    },
  ],
}
```

## Markdown to Richtext

If you have access to the Payload Config and the [lexical editor config](/docs/v3/rich-text/converters.md#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 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:

```ts
![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:

1. Upload your images to the Media collection first to get their document IDs
2. Pre-process your Markdown to replace `![alt] (url)` with `![media:<id>]()`
3. 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 `Banner` component.
- Can parse that MDX `Banner` back into a Lexical state.

![Shows the Banner field in a lexical editor and the MDX output](https://payloadcms.com/images/docs/mdx-example-light.png)

_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.
