---
title: Slate Editor
label: Slate (legacy)
order: 100
desc: The Slate editor has been supported by Payload since beta. It's very powerful and stores content as JSON, which unlocks a ton of power.
keywords: slatejs, slate, rich text, editor, headless cms
---

<Banner type="warning">

The [default Payload editor](/docs/rich-text/overview) is currently based on Lexical. This documentation
is about our old Slate-based editor. You can continue using it because it is still supported, or you can
see the optional [migration guide](/docs/rich-text/migration) to migrate from Slate to Lexical (recommended).

</Banner>

To use the Slate editor, first you need to install it:

```
npm install --save @payloadcms/richtext-slate
```

After installation, you can pass it to your top-level Payload Config:

```ts
import { buildConfig } from 'payload'
import { slateEditor } from '@payloadcms/richtext-slate'

export default buildConfig({
  collections: [
    // your collections here
  ],
  // Pass the Slate editor to the root config
  editor: slateEditor({}),
})
```

And here's an example for how to install the Slate editor on a field-by-field basis, while customizing its options:

```ts
import type { CollectionConfig } from 'payload'
import { slateEditor } from '@payloadcms/richtext-slate'

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'content',
      type: 'richText',
      // Pass the Slate editor here and configure it accordingly
      editor: slateEditor({
        admin: {
          elements: [
            // customize elements allowed in Slate editor here
          ],
          leaves: [
            // customize leaves allowed in Slate editor here
          ],
        },
      }),
    },
  ],
}
```

## Admin Options

**`elements`**

The `elements` property is used to specify which built-in or custom [SlateJS elements](https://docs.slatejs.org/concepts/02-nodes#element) should be made available to the field within the Admin Panel.

The default `elements` available in Payload are:

- `h1`
- `h2`
- `h3`
- `h4`
- `h5`
- `h6`
- `blockquote`
- `link`
- `ol`
- `ul`
- `textAlign`
- `indent`
- [`relationship`](#relationship-element)
- [`upload`](#upload-element)
- [`textAlign`](#text-align)

**`leaves`**

The `leaves` property specifies built-in or custom [SlateJS leaves](https://docs.slatejs.org/concepts/08-rendering#leaves) to be enabled within the Admin Panel.

The default `leaves` available in Payload are:

- `bold`
- `code`
- `italic`
- `strikethrough`
- `underline`

**`link.fields`**

This allows [fields](/docs/fields/overview) to be saved as extra fields on a link inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the link element.

`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).

![RichText link fields](https://payloadcms.com/images/docs/fields/richText/rte-link-fields-modal.jpg)
_RichText link with custom fields_

**`upload.collections[collection-name].fields`**

This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.

![RichText upload element](https://payloadcms.com/images/docs/fields/richText/rte-upload-element.jpg)
_RichText field using the upload element_

![RichText upload element modal](https://payloadcms.com/images/docs/fields/richText/rte-upload-fields-modal.jpg)
_RichText upload element modal displaying fields from the config_

### Relationship element

The built-in `relationship` element is a powerful way to reference other Documents directly within your Rich Text editor.

### Upload element

Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.

<Banner type="success">
  **Tip:**

Collections are automatically allowed to be selected within the Rich Text relationship and upload
elements by default. If you want to disable a collection from being able to be referenced in Rich
Text fields, set the collection admin options of **enableRichTextLink** and
**enableRichTextRelationship** to false.

</Banner>

Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.

### TextAlign element

Text Alignment is not included by default and can be added to a Rich Text Editor by adding `textAlign` to the list of elements. TextAlign will alter the existing element to include a new `textAlign` field in the resulting JSON. This field can be used in combination with other elements and leaves to position content to the left, center or right.

### Specifying which elements and leaves to allow

To specify which default elements or leaves should be allowed to be used for this field, define arrays that contain string names for each element or leaf you wish to enable. To specify a custom element or leaf, pass an object with all corresponding properties as outlined below. View the [example](#example) to reference how this all works.

### Building custom elements and leaves

You can design and build your own Slate elements and leaves to extend the editor with your own functionality. To do so, first start by reading the [SlateJS documentation](https://docs.slatejs.org/) and looking at the [Slate examples](https://www.slatejs.org/examples/richtext) to familiarize yourself with the SlateJS editor as a whole.

Once you're up to speed with the general concepts involved, you can pass in your own elements and leaves to your field's admin config.

**Both custom elements and leaves are defined via the following config:**

| Property        | Description                                                |
| --------------- | ---------------------------------------------------------- |
| **`name`** \*   | The default name to be used as a `type` for this element.  |
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
| **`plugins`**   | An array of plugins to provide to the Rich Text editor.    |
| **`type`**      | A type that overrides the default type used by `name`      |

Custom `Element`s also require the `Element` property set to a React component to be rendered as the `Element` within the rich text editor itself.

Custom `Leaf` objects follow a similar pattern but require you to define the `Leaf` property instead.

Specifying custom `Type`s let you extend your custom elements by adding additional fields to your JSON object.

### Example

`collections/ExampleCollection.ts`

```ts
import type { CollectionConfig } from 'payload'

import { slateEditor } from '@payloadcms/richtext-slate'

export const ExampleCollection: CollectionConfig = {
  slug: 'example-collection',
  fields: [
    {
      name: 'content', // required
      type: 'richText', // required
      defaultValue: [
        {
          children: [{ text: 'Here is some default content for this field' }],
        },
      ],
      required: true,
      editor: slateEditor({
        admin: {
          elements: [
            'h2',
            'h3',
            'h4',
            'link',
            'blockquote',
            {
              name: 'cta',
              Button: CustomCallToActionButton,
              Element: CustomCallToActionElement,
              plugins: [
                // any plugins that are required by this element go here
              ],
            },
          ],
          leaves: [
            'bold',
            'italic',
            {
              name: 'highlight',
              Button: CustomHighlightButton,
              Leaf: CustomHighlightLeaf,
              plugins: [
                // any plugins that are required by this leaf go here
              ],
            },
          ],
          link: {
            // Inject your own fields into the Link element
            fields: [
              {
                name: 'rel',
                label: 'Rel Attribute',
                type: 'select',
                hasMany: true,
                options: ['noopener', 'noreferrer', 'nofollow'],
              },
            ],
          },
          upload: {
            collections: {
              media: {
                fields: [
                  // any fields that you would like to save
                  // on an upload element in the `media` collection
                ],
              },
            },
          },
        },
      }),
    },
  ],
}
```

### Generating HTML

As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:

```ts
import React, { Fragment } from "react";
import escapeHTML from "escape-html";
import { Text } from "slate";

const serialize = (children) =>
  children.map((node, i) => {
    if (Text.isText(node)) {
      let text = (
        <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
      );

      if (node.bold) {
        text = <strong key={i}>{text}</strong>;
      }

      if (node.code) {
        text = <code key={i}>{text}</code>;
      }

      if (node.italic) {
        text = <em key={i}>{text}</em>;
      }

      // Handle other leaf types here...

      return <Fragment key={i}>{text}</Fragment>;
    }

    if (!node) {
      return null;
    }

    switch (node.type) {
      case "h1":
        return <h1 key={i}>{serialize(node.children)}</h1>;
      // Iterate through all headings here...
      case "h6":
        return <h6 key={i}>{serialize(node.children)}</h6>;
      case "blockquote":
        return <blockquote key={i}>{serialize(node.children)}</blockquote>;
      case "ul":
        return <ul key={i}>{serialize(node.children)}</ul>;
      case "ol":
        return <ol key={i}>{serialize(node.children)}</ol>;
      case "li":
        return <li key={i}>{serialize(node.children)}</li>;
      case "link":
        return (
          <a href={escapeHTML(node.url)} key={i}>
            {serialize(node.children)}
          </a>
        );

      default:
        return <p key={i}>{serialize(node.children)}</p>;
    }
  });
```

<Banner>
  **Note:**

The above example is for how to render to JSX, although for plain HTML the pattern is similar.
Just remove the JSX and return HTML strings instead!

</Banner>

### Built-in SlateJS Plugins

Payload comes with a few built-in SlateJS plugins which can be extended to make developing your own elements and leaves a bit easier.

#### `shouldBreakOutOnEnter`

Payload's built-in heading elements all allow a "hard return" to "break out" of the currently active element. For example, if you hit `enter` while typing an `h1`, the `h1` will be "broken out of" and you'll be able to continue writing as the default paragraph element.

If you want to utilize this functionality within your own custom elements, you can do so by adding a custom plugin to your `element` like the following "large body" element example:

`customLargeBodyElement.js`:

```ts
import Button from './Button'
import Element from './Element'
import withLargeBody from './plugin'

export default {
  name: 'large-body',
  Button,
  Element,
  plugins: [
    (incomingEditor) => {
      const editor = incomingEditor
      const { shouldBreakOutOnEnter } = editor

      editor.shouldBreakOutOnEnter = (element) =>
        element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)

      return editor
    },
  ],
}
```

Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin.

The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate function to add its own element name to the list of elements that should "break out" when the `enter` key is pressed.

### TypeScript

If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:

```ts
import type {
  RichTextCustomElement,
  RichTextCustomLeaf,
} from '@payloadcms/richtext-slate'
```
