---
title: "Rich Text — Converting to JSX (Lexical)"
aliases: [richtext-jsx, lexical-jsx, rich-text-jsx-converter]
tags: [payloadcms, lexical, rich-text, jsx, react, converters]
sources: [raw/rich-text__converting-jsx.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

To render Lexical rich text in a React frontend, use the `RichText` component from `@payloadcms/richtext-lexical/react`. It accepts a `SerializedEditorState` and converts it to JSX using built-in node converters.

```tsx
import { RichText } from '@payloadcms/richtext-lexical/react'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  return <RichText data={data} />
}
```

## Custom Converters

Pass a `converters` function to override or extend the default node converters. The function receives `{ defaultConverters }` and must return a merged converters object.

```tsx
const jsxConverters: JSXConvertersFunction<DefaultNodeTypes> = ({
  defaultConverters,
}) => ({
  ...defaultConverters,
  // add custom converters here
})

<RichText converters={jsxConverters} data={lexicalData} />
```

## Internal Links

By default Payload has no way to resolve internal document links to URLs — you'll see a console error:  
> *"found internal link, but internalDocToHref is not provided"*

Fix: supply `internalDocToHref` to `LinkJSXConverter`:

```tsx
import { LinkJSXConverter } from '@payloadcms/richtext-lexical/react'

const internalDocToHref = ({ linkNode }) => {
  const { relationTo, value } = linkNode.fields.doc!
  switch (relationTo) {
    case 'posts': return `/posts/${value.slug}`
    case 'pages': return `/${value.slug}`
    default:      return `/${relationTo}/${value.slug}`
  }
}

const jsxConverters = ({ defaultConverters }) => ({
  ...defaultConverters,
  ...LinkJSXConverter({ internalDocToHref }),
})
```

## Custom Blocks in Rich Text

If the rich text field contains custom `blocks` or `inlineBlocks`, you must register a converter per slug — there is no default:

```tsx
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
  ...defaultConverters,
  blocks: {
    myNumberBlock: ({ node }) => <div>{node.fields.number}</div>,
    myTextBlock:   ({ node }) => <div>{node.fields.text}</div>,
  },
  inlineBlocks: {
    myInlineBlock: ({ node }) => <span>{node.fields.text}</span>,
  },
})
```

- Add `'use client'` if the converter uses client-side APIs.
- Extend `NodeTypes` with `SerializedBlockNode<YourBlock> | SerializedInlineBlockNode<YourInlineBlock>` for full type safety.

## Overriding Built-In Converters

Override any default converter by keying to its node type. Example — use `next/image` for uploads:

```tsx
const jsxConverters: JSXConvertersFunction<DefaultNodeTypes> = ({ defaultConverters }) => ({
  ...defaultConverters,
  upload: ({ node }) => {
    if (node.relationTo === 'uploads' && typeof node.value === 'object') {
      const { alt, height, url, width } = node.value
      return <Image alt={alt} height={height} src={url} width={width} />
    }
    return null
  },
})
```

## Sharing Node Rendering with Admin Panel

If you need the same node rendering in both the admin panel and your frontend, use [[wiki/payloadcms/rich-text-views|Views]] — a single node map that works in both contexts — instead of duplicating JSX converters.

## Key Takeaways

- Import `RichText` from `@payloadcms/richtext-lexical/react`; pass `SerializedEditorState` as `data`.
- Default converters handle common nodes; extend via the `converters` prop.
- Internal links require `internalDocToHref` passed to `LinkJSXConverter` — otherwise a console error appears and links break.
- Custom blocks must have a converter per block slug; no converter = silent omission.
- Override any built-in converter (e.g. upload → `next/image`) by keying to the node type.
- Ensure `depth` is high enough when fetching data so Lexical nodes (uploads, relationships) are fully populated.
- For shared admin+frontend rendering, prefer [[wiki/payloadcms/rich-text-views|Views]] over duplicating converters.

## Related

- [[wiki/payloadcms/rich-text-converters|Rich Text — Converters (Lexical)]] — HTML/Markdown/Plaintext converters and `editorConfigFactory`
- [[wiki/payloadcms/rich-text-converting-html|Rich Text — Converting HTML]] — `convertLexicalToHTML` / `convertHTMLToLexical`
- [[wiki/payloadcms/rich-text-blocks|Rich Text — Blocks (Lexical BlocksFeature)]] — embedding Payload Blocks in Lexical
- [[wiki/payloadcms/fields-rich-text|Rich Text Field]] — field config reference
- [[wiki/payloadcms/queries-depth|Queries — Depth]] — depth setting for relationship population

## Sources

- `raw/rich-text__converting-jsx.md`
- Official docs: https://payloadcms.com/docs/rich-text/converting-jsx
