Serializing HTML
HTML <-> Slate
'use client';
import React from 'react';
import { Plate } from '@udecode/plate-common/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>
);
}
Note: Round-tripping is not yet supported: the HTML serializer will not preserve all information from the Slate value when converting to HTML and back.
Slate -> HTML
Usage
// ...
import { createSlateEditor, serializeHtml } from '@udecode/plate-common';
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>`;
Using Static Components
When serializing Slate content to HTML, you must use static versions of your components. Your interactive, "client" components often rely on browser APIs or client-side React features that are not available during server-side rendering (SSR).
What does this mean?
- Interactive Components: Components that use
use client
, event handlers (onClick
), or browser-only APIs (likewindow
ordocument
) cannot run on the server. - Static Components: Instead, for serialization, you must provide "static" versions of these components. Static components should:
- Not include any event handlers like
onClick
. - Not rely on browser APIs or client-side hooks.
- Simply render the content as plain HTML.
- Not include any event handlers like
Example:
If you have a client-only component like ImageElement
that uses use client
and event handlers, you need to create a static version ImageElementStatic
that just returns a plain <img>
tag with all its attributes and no interactivity.
For each plugin key, provide its corresponding static component in the components
object passed to serializeHtml
:
// Instead of using interactive components that rely on 'use client',
// use statically defined components that simply return plain HTML.
import { createSlateEditor, serializeHtml } from '@udecode/plate-common';
// Import static versions of your components
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';
// ... and so on for each plugin ...
const editor = createSlateEditor({ /* ...plugins... */ });
const components = {
[BaseParagraphPlugin.key]: ParagraphElementStatic,
[BaseHeadingPlugin.key]: HeadingElementStatic,
[BaseImagePlugin.key]: ImageElementStatic,
// ... other static components ...
};
const html = await serializeHtml(editor, { components });
PlateStatic vs. Plate
When rendering your editor output without the need for an interactive environment, you have two primary options: PlateStatic
and Plate
. Both are designed to produce static, non-editable views of your content, but they differ in purpose and performance characteristics.
PlateStatic
- Purpose: A more lightweight, server-compatible static renderer of the editor content.
- Performance: Generally more performant than
Plate
in read-only mode. - Use Case: Ideal when you need a static preview of the content (e.g., in a server-side render or a static build).
PlateStatic
should be the default choice if you want a fast, static representation of your content.
Plate (Read-Only)
- Purpose: The standard
Plate
editor component used withreadOnly={true}
. While this prevents editing, it still operates as a client component relying on browser APIs. - Use Case: Suitable if you’re rendering in a browser and want an interactive environment (e.g. comments) that just happens to be non-editable. It’s not suitable for server-side HTML serialization or scenarios where static output is required, as it still runs client-side code.
In summary:
- If you need high performance, a static view or HTML serialization: Use
PlateStatic
. - If you need an interactive environment and can run in a browser, but just want it read-only: Use
Plate
withreadOnly={true}
.
API
serializeHtml
Convert Slate Nodes into HTML string.
Parameters
Options to control the HTML serialization process.
Returns
A HTML string representing the Slate content.
HTML -> Slate
Usage
The editor.api.html.deserialize
function allows you to convert HTML content into Slate value:
import { createPlateEditor } from '@udecode/plate-common/react';
const editor = createPlateEditor({
plugins: [
// all plugins that you want to deserialize
]
})
editor.children = editor.api.html.deserialize('<p>Hello, world!</p>')
Plugin Deserialization Rules
Here's a comprehensive list of plugins that support HTML deserialization, along with their corresponding HTML elements and styles:
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>
- Parses
aria-level
attribute for indentation
- List Item:
Blocks
- BlockquotePlugin:
<blockquote>
- CodeBlockPlugin:
- Deserializes
<pre>
elements - Deserializes
<p>
elements withfontFamily: 'Consolas'
style - Splits content into code lines
- Preserves language information if available
- Deserializes
- HorizontalRulePlugin:
<hr>
Links and Media
- LinkPlugin:
<a>
- ImagePlugin:
<img>
- MediaEmbedPlugin:
<iframe>
Tables
- TablePlugin:
- Table:
<table>
- Table Row:
<tr>
- Table Cell:
<td>
- Table Header Cell:
<th>
- Table:
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
Extending Deserialization
You can extend or customize the deserialization behavior of any plugin. Here's an example of how you might extend the CodeBlockPlugin
:
import { CodeBlockPlugin } from '@udecode/plate-code-block/react';
const CustomCodeBlockPlugin = 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: CodeLinePlugin.key,
children: [{ text: line }],
})),
};
},
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
editor.api.html.deserialize
Deserialize HTML string into Slate value.
Parameters
- Default:
true
(Whitespace will be removed.)
options.element HTMLElement | string
The HTML element or string to deserialize.
options.collapseWhiteSpace optional boolean
Flag to enable or disable the removal of whitespace from the serialized HTML.
Returns
The deserialized Slate value.