Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Lexical Migration

Migrating from Slate

While both Slate and Lexical save the editor state in JSON, the structure of the JSON is different.

Migration via Migration Script (Recommended)

Just import the migrateSlateToLexical function we provide, pass it the payload object and run it. Depending on the amount of collections, this might take a while.

IMPORTANT: This will overwrite all slate data. We recommend doing the following first:

  1. Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.
  2. Make every richText field a lexical editor. This script will only convert lexical richText fields with old Slate data
  3. Add the SlateToLexicalFeature (as seen below) first, and test it out by loading up the Admin Panel, to see if the migrator works as expected. You might have to build some custom converters for some fields first in order to convert custom Slate nodes. The SlateToLexicalFeature is where the converters are stored. Only fields with this feature added will be migrated.
  4. If this works as expected, add the disableHooks: true prop everywhere you're initializing SlateToLexicalFeature. Example: SlateToLexicalFeature({ disableHooks: true }). Once you did that, you're ready to run the migration script.
1
import { migrateSlateToLexical } from '@payloadcms/richtext-lexical/migrate'
2
3
await migrateSlateToLexical({ payload })

Migration via SlateToLexicalFeature

One way to handle this is to just give your lexical editor the ability to read the slate JSON.

Simply add the SlateToLexicalFeature to your editor:

1
import type { CollectionConfig } from 'payload'
2
3
import { SlateToLexicalFeature } from '@payloadcms/richtext-lexical/migrate'
4
import { lexicalEditor } from '@payloadcms/richtext-lexical'
5
6
const Pages: CollectionConfig = {
7
slug: 'pages',
8
fields: [
9
{
10
name: 'nameOfYourRichTextField',
11
type: 'richText',
12
editor: lexicalEditor({
13
features: ({ defaultFeatures }) => [...defaultFeatures, SlateToLexicalFeature({})],
14
}),
15
},
16
],
17
}

and done! Now, every time this lexical editor is initialized, it converts the slate date to lexical on-the-fly. If the data is already in lexical format, it will just pass it through.

This is by far the easiest way to migrate from Slate to Lexical, although it does come with a few caveats:

  • There is a performance hit when initializing the lexical editor
  • The editor will still output the Slate data in the output JSON, as the on-the-fly converter only runs for the Admin Panel

The easy way to solve this: Edit the richText field and save the document! This overrides the slate data with the lexical data, and the next time the document is loaded, the lexical data will be used. This solves both the performance and the output issue for that specific document. This, however, is a slow and gradual migration process, thus you will have to support both API formats. Especially for a large number of documents, we recommend running the migration script, as explained above.

Converting custom Slate nodes

If you have custom Slate nodes, create a custom converter for them. Here's the Upload converter as an example:

1
import type { SerializedUploadNode } from '../uploadNode'
2
import type { SlateNodeConverter } from '@payloadcms/richtext-lexical/migrate'
3
4
export const SlateUploadConverter: SlateNodeConverter = {
5
converter({ slateNode }) {
6
return {
7
fields: {
8
...slateNode.fields,
9
},
10
format: '',
11
relationTo: slateNode.relationTo,
12
type: 'upload',
13
value: {
14
id: slateNode.value?.id || '',
15
},
16
version: 1,
17
} as const as SerializedUploadNode
18
},
19
nodeTypes: ['upload'],
20
}

It's pretty simple: You get a Slate node as input, and you return the lexical node. The nodeTypes array is used to determine which Slate nodes this converter can handle.

When using a migration script, you can add your custom converters to the converters property of the convertSlateToLexical props, as seen in the example above

When using the SlateToLexicalFeature, you can add your custom converters to the converters property of the SlateToLexicalFeature props:

1
import type { CollectionConfig } from 'payload'
2
3
import { lexicalEditor } from '@payloadcms/richtext-lexical'
4
import {
5
SlateToLexicalFeature,
6
defaultSlateConverters,
7
} from '@payloadcms/richtext-lexical'
8
9
import { YourCustomConverter } from '../converters/YourCustomConverter'
10
11
const Pages: CollectionConfig = {
12
slug: 'pages',
13
fields: [
14
{
15
name: 'nameOfYourRichTextField',
16
type: 'richText',
17
editor: lexicalEditor({
18
features: ({ defaultFeatures }) => [
19
...defaultFeatures,
20
SlateToLexicalFeature({
21
converters: [...defaultSlateConverters, YourCustomConverter],
22
}),
23
],
24
}),
25
},
26
],
27
}

Migrating from payload-plugin-lexical

Migrating from payload-plugin-lexical works similar to migrating from Slate.

Instead of a SlateToLexicalFeature there is a LexicalPluginToLexicalFeature you can use. And instead of convertSlateToLexical you can use convertLexicalPluginToLexical.

Migrating lexical data from old version to new version

Each lexical node has a version property which is saved in the database. Every time we make a breaking change to the node's data, we increment the version. This way, we can detect an old version and automatically convert old data to the new format once you open up the editor.

The problem is, this migration only happens when you open the editor, modify the richText field (so that the field's setValue function is called) and save the document. Until you do that for all documents, some documents will still have the old data.

To solve this, we export an upgradeLexicalData function which goes through every single document in your Payload app and re-saves it, if it has a lexical editor. This way, the data is automatically converted to the new format, and that automatic conversion gets applied to every single document in your app.

IMPORTANT: Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.

1
import { upgradeLexicalData } from '@payloadcms/richtext-lexical'
2
3
await upgradeLexicalData({ payload })
Next

Lexical Building Custom Features