Serializing HTML
'use client';
import React from 'react';
import { Plate } from '@udecode/plate/react';
import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';
import { DEMO_VALUES } from './values/demo-values';
export default function Demo({ id }: { id: string }) {
const editor = useCreateEditor({
plugins: [...editorPlugins],
value: DEMO_VALUES[id],
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
Slate -> HTML
Usage
// ...
import { createSlateEditor, serializeHtml } from '@udecode/plate';
import { EditorStatic } from '@/components/plate-ui/editor-static';
// Create an editor and configure all the plugins you need
const editor = createSlateEditor({
// ... your plugins ...
});
// Provide the components that map Slate nodes to HTML elements
const components = {
// [ParagraphPlugin.key]: ParagraphElementStatic,
// [HeadingPlugin.key]: HeadingElementStatic,
// ...
};
// You can also pass a custom editor component and props.
// For example, EditorStatic is a styled version of PlateStatic.
const html = await serializeHtml(editor, {
components,
editorComponent: EditorStatic, // defaults to PlateStatic if not provided
props: { variant: 'none', className: 'p-2' },
});
If you use a custom component, like EditorStatic, you must also ensure that all required styles and classes are included in your final HTML file. Since serialize only returns the inner editor HTML, you may need to wrap it in a full HTML document with any external CSS, scripts, or <style>
tags.
For example:
// After serializing the content:
const html = await serializeHtml(editor, {
components,
editorComponent: EditorStatic,
props: { variant: 'none' },
});
// Wrap the HTML in a full HTML document
const fullHtml = `<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/path/to/tailwind.css" />
<!-- other head elements -->
</head>
<body>
${html}
</body>
</html>`;
Note: The serialization process converts Slate nodes into static HTML markup. Interactivity like React event handlers, client-side hooks, or components that rely on browser APIs won't work in the serialized output.
Using Static Components
When serializing Slate content to HTML, you must use static versions of your elements (i.e., no client-only code). Check out the Plate Static guide for more details on creating a “static” version of your components that can safely run in a server environment.
Example:
import { createSlateEditor, serializeHtml } from '@udecode/plate';
import { ParagraphElementStatic } from '@/components/plate-ui/paragraph-element-static';
import { HeadingElementStatic } from '@/components/plate-ui/heading-element-static';
import { ImageElementStatic } from '@/components/plate-ui/image-element-static';
// ... etc.
const editor = createSlateEditor({ /* your plugins */ });
const components = {
[BaseParagraphPlugin.key]: ParagraphElementStatic,
[BaseHeadingPlugin.key]: HeadingElementStatic,
[BaseImagePlugin.key]: ImageElementStatic,
};
const html = await serializeHtml(editor, { components });
For a more complete static-render approach, see Plate Static.
HTML -> Slate
The HTML deserializer supports round-trip conversion, meaning you can export content to HTML and later import it back while maintaining the document structure, formatting, and attributes.
Usage
The editor.api.html.deserialize
function converts HTML content into a Slate value:
import { createPlateEditor } from '@udecode/plate/react';
const editor = createPlateEditor({
plugins: [
// all plugins that you want to deserialize
],
});
// You can deserialize HTML strings
editor.children = editor.api.html.deserialize('<p>Hello, world!</p>');
// Or deserialize HTML previously exported from Plate
const exportedHtml = await serializeHtml(editor, { components });
const importedValue = editor.api.html.deserialize(exportedHtml);
editor.children = importedValue;
Plugin Deserialization Rules
Below is a reference for the built-in HTML deserialization rules. Each plugin can parse specific tags, styles, and attributes.
Text Formatting
- BoldPlugin:
<strong>
,<b>
, orstyle="font-weight: 600|700|bold"
- ItalicPlugin:
<em>
,<i>
, orstyle="font-style: italic"
- UnderlinePlugin:
<u>
orstyle="text-decoration: underline"
- StrikethroughPlugin:
<s>
,<del>
,<strike>
, orstyle="text-decoration: line-through"
- SubscriptPlugin:
<sub>
orstyle="vertical-align: sub"
- SuperscriptPlugin:
<sup>
orstyle="vertical-align: super"
- CodePlugin:
<code>
orstyle="font-family: Consolas"
(not within a<pre>
tag) - KbdPlugin:
<kbd>
Paragraphs and Headings
- ParagraphPlugin:
<p>
- HeadingPlugin:
<h1>
,<h2>
,<h3>
,<h4>
,<h5>
,<h6>
Lists
- ListPlugin:
- Unordered List:
<ul>
- Ordered List:
<ol>
- List Item:
<li>
- Unordered List:
- IndentListPlugin:
- List Item:
<li>
- Uses
aria-level
for indentation
- List Item:
Blocks
- BlockquotePlugin:
<blockquote>
- CodeBlockPlugin:
<pre>
elements<p>
elements withfontFamily: 'Consolas'
- Splits content into code lines
- Preserves language info if available
- HorizontalRulePlugin:
<hr>
Links and Media
- LinkPlugin:
<a>
- ImagePlugin:
<img>
- MediaEmbedPlugin:
<iframe>
Tables
- TablePlugin:
<table>
<tr>
<td>
<th>
Text Styling
- FontBackgroundColorPlugin:
style="background-color: *"
- FontColorPlugin:
style="color: *"
- FontFamilyPlugin:
style="font-family: *"
- FontSizePlugin:
style="font-size: *"
- FontWeightPlugin:
style="font-weight: *"
- HighlightPlugin:
<mark>
Layout and Formatting
- AlignPlugin:
style="text-align: *"
- LineHeightPlugin:
style="line-height: *"
Deserialization Properties
Plugins can define various properties to control HTML deserialization:
parse
: A function to parse an HTML element into a Plate nodequery
: A function that determines if the deserializer should be appliedrules
: An array of rule objects that define valid HTML elements and attributesisElement
: Indicates if the plugin deserializes elementsisLeaf
: Indicates if the plugin deserializes leaf nodesattributeNames
: List of HTML attribute names to store innode.attributes
withoutChildren
: Excludes child nodes from deserializationrules
: Array of rule objects for element matchingvalidAttribute
: Valid element attributesvalidClassName
: Valid CSS class namevalidNodeName
: Valid HTML tag namesvalidStyle
: Valid CSS styles
Customizing Deserialization
You can modify how plugins parse HTML:
import { CodeBlockPlugin } from '@udecode/plate-code-block/react';
const MyCodeBlockPlugin = CodeBlockPlugin.extend({
parsers: {
html: {
deserializer: {
parse: ({ element }) => {
const language = element.getAttribute('data-language');
const textContent = element.textContent || '';
const lines = textContent.split('\n');
return {
type: CodeBlockPlugin.key,
language,
children: lines.map((line) => ({
type: 'code_line',
children: [{ text: line }],
})),
};
},
rules: [
// inherit existing rules
...CodeBlockPlugin.parsers.html.deserializer.rules,
{ validAttribute: 'data-language' },
],
},
},
},
});
This customization adds support for a data-language
attribute in code block deserialization and preserves the language information.
Advanced Deserialization Example
The IndentListPlugin
provides a more complex deserialization process:
- It transforms HTML list structures into indented paragraphs.
- It handles nested lists by preserving the indentation level.
- It uses the
aria-level
attribute to determine the indentation level.
Here's a simplified version of its deserialization logic:
export const IndentListPlugin = createTSlatePlugin<IndentListConfig>({
// ... other configurations ...
parsers: {
html: {
deserializer: {
isElement: true,
parse: ({ editor, element, getOptions }) => ({
indent: Number(element.getAttribute('aria-level')),
listStyleType: element.style.listStyleType,
type: editor.getType(ParagraphPlugin),
}),
rules: [
{
validNodeName: 'LI',
},
],
},
},
},
});
API
serializeHtml(editor, options)
Converts Slate nodes from editor.children
into an HTML string.
const htmlString = await serializeHtml(editor, {
components,
stripClassNames: true,
preserveClassNames: ['slate-'],
stripDataAttributes: true,
editorComponent: MyCustomStaticWrapper,
props: { className: 'my-serialized' },
});
- Default:
false
- Default:
false
A mapping of plugin keys to React components. Each component renders the corresponding Slate node as HTML.
A React component to render the entire editor content. Defaults to PlateStatic
if not provided. This component receives components
, editor
, and props
.
Props to pass to the editorComponent
. The generic type T
extends PlateStaticProps
.
A list of class name prefixes to preserve if stripClassNames
is enabled.
If true
, remove class names from the output HTML except those listed in preserveClassNames
.
If true
, remove data-*
attributes from the output HTML.
editor.api.html.deserialize
Parses an HTML string or element into a Slate value.
editor.children = editor.api.html.deserialize('<p>Deserialized</p>', {
collapseWhiteSpace: false,
});