---
title: "Rich Text — Rendering On Demand (RenderLexical)"
aliases: [render-lexical, lexical-on-demand, rich-text-on-demand]
tags: [payloadcms, lexical, rich-text, react, rsc, experimental]
sources: [raw/rich-text__rendering-on-demand.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

`RenderLexical` is an **experimental** client component that renders a Lexical editor on demand — without requiring the editor to be embedded inside a full `Form`. Under the hood it calls a server action (`render-field`) to bootstrap the editor on the server, which resolves nested field schemas (blocks, tables, link drawers, upload fields) before hydrating on the client.

> **Warning:** `RenderLexical` and the `render-field` server function are experimental and may change in minor releases.

### Why it exists — three historical pain points

| Pain Point | Root Cause |
|------------|-----------|
| Can't render from the client | Lexical is a React Server Component (RSC) |
| Missing nested schemas | Blocks/tables/drawers need server-side field shapes at render time |
| Form setup overhead | Rich text field expected to live inside a `Form` |

---

## Usage

### Inside an existing Form

Use when you already have a `Form` context:

```tsx
'use client'
import type { JSONFieldClientComponent } from 'payload'
import { buildEditorState, RenderLexical } from '@payloadcms/richtext-lexical/client'
import { lexicalFullyFeaturedSlug } from '../../slugs.js'

export const Component: JSONFieldClientComponent = (args) => (
  <RenderLexical
    field={{ name: 'myFieldName' }} // must match the field name in your form
    initialValue={buildEditorState<DefaultNodeTypes>({ text: 'default value' })}
    schemaPath={`collection.${lexicalFullyFeaturedSlug}.richText`}
  />
)
```

### Outside a Form (controlled state)

Manually manage editor state with `useState`:

```tsx
'use client'
import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
import { buildEditorState, RenderLexical } from '@payloadcms/richtext-lexical/client'
import React, { useState } from 'react'

export const Component = () => {
  const [value, setValue] = useState<DefaultTypedEditorState | undefined>(
    () => buildEditorState<DefaultNodeTypes>({ text: 'state default' }),
  )

  return (
    <div>
      <RenderLexical
        field={{ name: 'myField' }}
        initialValue={buildEditorState<DefaultNodeTypes>({ text: 'default value' })}
        schemaPath={`collection.${lexicalFullyFeaturedSlug}.richText`}
        setValue={setValue as any}
        value={value}
      />
      <button onClick={() => setValue(buildEditorState({ text: 'state default' }))}>
        Reset
      </button>
    </div>
  )
}
```

---

## Props Reference

| Prop | Required | Description |
|------|----------|-------------|
| `field.name` | Yes | Field name — must match the form field name if inside a `Form` |
| `initialValue` | Yes | Initial editor state — use `buildEditorState()` helper |
| `schemaPath` | Yes | Server path to look up nested field schemas |
| `value` | No | Controlled state (for outside-Form usage) |
| `setValue` | No | State setter (for outside-Form usage) |

---

## Choosing the `schemaPath`

`schemaPath` tells the server which richText field to render, giving it the exact nested schemas (blocks, relationship drawers, upload fields, tables, etc.).

**Format:**
- `collection.<collectionSlug>.<fieldPath>`
- `global.<globalSlug>.<fieldPath>`

**Examples:**
- Top level: `collection.posts.richText`
- Nested in group/tab: `collection.posts.content.richText`

**Tip — hidden anchor field:** If your target editor lives deep in arrays/blocks and you're unsure of the exact path, define a hidden top-level richText as a "render anchor":

```ts
{
  name: 'onDemandAnchor',
  type: 'richText',
  admin: { hidden: true },
}
```

Then use `schemaPath="collection.posts.onDemandAnchor"`.

---

## Key Takeaways

- `RenderLexical` lets you render a Lexical editor **outside a full `Form`** — ideal for custom admin components and standalone editor widgets
- It uses a **server action** to resolve all nested field schemas at mount time — blocks, tables, drawers all work correctly
- `buildEditorState()` is the correct way to create an initial `DefaultTypedEditorState` value
- `schemaPath` is required — it must point to a real richText field in your Payload config
- Both **inside-Form** (field name binding) and **outside-Form** (controlled `value`/`setValue`) modes are supported
- This is **experimental** — expect breaking changes in minor releases

---

## Related Articles

- [[wiki/payloadcms/rich-text|Rich Text (Lexical) — Overview]]
- [[wiki/payloadcms/rich-text-official-features|Rich Text — Official Lexical Features]]
- [[wiki/payloadcms/rich-text-blocks|Rich Text — Blocks (BlocksFeature)]]
- [[wiki/payloadcms/fields-rich-text|Rich Text Field Config]]
- [[wiki/payloadcms/custom-components-authoring|Custom Components — Authoring Guide]]
- [[wiki/payloadcms/react-hooks|React Hooks]]

---

## Sources

- `raw/rich-text__rendering-on-demand.md`
- https://payloadcms.com/docs/rich-text/rendering-on-demand
