From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting

HTML

PreviousNext

Convert Plate content to HTML and vice-versa.

Use renderStaticHtml to render a configured static Plate tree to HTML. Use editor.api.html.deserialize(...) to parse HTML into Plate content. Static React rendering and semantic HTML parsing have different owners, so this guide treats each direction separately.

Loading…
Static RenderingMarkdown

On This Page

Kit UsageInstallationAdd KitExamplePlate to HTMLBasic UsageStyling Serialized HTMLUsing Static ComponentsHTML to PlateBasic UsagePlugin HTML Codec OverviewdefineCodecsWhole-Input HTML HooksAPI ReferencerenderStaticHtml(editor, options)editor.api.html.deserialize(options)Next Steps
Build your editor
Production-ready AI template and reusable components.
Get all-access

Kit Usage

Installation

The fastest way to enable HTML serialization is with the BaseEditorKit, which includes pre-configured base plugins that support HTML conversion for most common elements and marks.

import { BaseAlignKit } from './align-static';
import { BaseBasicBlocksKit } from './basic-blocks-static';
import { BaseBasicMarksKit } from './basic-marks-static';
import { BaseCalloutKit } from './callout-static';
import { BaseCodeBlockKit } from './code-block-static';
import { BaseColumnKit } from './column-static';
import { BaseCommentKit } from './comment-static';
import { BaseDateKit } from './date-static';
import { BaseFontKit } from './font-static';
import { BaseFootnoteKit } from './footnote-static';
import { BaseLineHeightKit } from './line-height-static';
import { BaseLinkKit } from './link-static';
import { BaseListKit } from './list-static';
import { MarkdownKit } from './markdown';
import { BaseMathKit } from './math-static';
import { BaseMediaKit } from './media-static';
import { BaseMentionKit } from './mention-static';
import { BaseSuggestionKit } from './suggestion-static';
import { BaseTableKit } from './table-static';
import { BaseTocKit } from './toc-static';
import { BaseToggleKit } from './toggle-static';
 
export const BaseEditorKit = [
  ...BaseBasicBlocksKit,
  ...BaseCodeBlockKit,
  ...BaseTableKit,
  ...BaseToggleKit,
  ...BaseTocKit,
  ...BaseMediaKit,
  ...BaseCalloutKit,
  ...BaseColumnKit,
  ...BaseMathKit,
  ...BaseDateKit,
  ...BaseLinkKit,
  ...BaseMentionKit,
  ...BaseFootnoteKit,
  ...BaseBasicMarksKit,
  ...BaseFontKit,
  ...BaseListKit,
  ...BaseAlignKit,
  ...BaseLineHeightKit,
  ...BaseCommentKit,
  ...BaseSuggestionKit,
  ...MarkdownKit,
] as const;
import { BaseAlignKit } from './align-static';
import { BaseBasicBlocksKit } from './basic-blocks-static';
import { BaseBasicMarksKit } from './basic-marks-static';
import { BaseCalloutKit } from './callout-static';
import { BaseCodeBlockKit } from './code-block-static';
import { BaseColumnKit } from './column-static';
import { BaseCommentKit } from './comment-static';
import { BaseDateKit } from './date-static';
import { BaseFontKit } from './font-static';
import { BaseFootnoteKit } from './footnote-static';
import { BaseLineHeightKit } from './line-height-static';

































Add Kit

import { createBaseEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
 
const editor = createBaseEditor({
  plugins: BaseEditorKit,
  initialValue: [
    { type: 'heading', level: 1, children: [{ text: 'Hello World' }] },
    { type: 'paragraph', children: [{ text: 'This content will be serialized to HTML.' }] },
  ],
});
 
// Serialize to HTML
const html = await renderStaticHtml(editor);

Example

See a complete server-side HTML generation example:

import fs from 'node:fs/promises';
import path from 'node:path';
 
import { cva } from 'class-variance-authority';
import type { Metadata } from 'next';
import type { EditorDocumentValue } from 'platejs';
import { createStaticEditor, renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import { EditorStatic } from '@/components/editor/editor-static';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
















































































































































Plate to HTML

Convert Plate editor content (Plate nodes) into an HTML string. This is often done server-side.

View Server-Side Example

Key Server-Side Constraint

When using renderStaticHtml or other Plate utilities in a server environment (Node.js, RSC), you must not import from /react subpaths of any platejs* package. Always use the base imports (e.g., @platejs/basic-nodes instead of @platejs/basic-nodes/react).

This means you should use createBaseEditor from platejs for server-side editor instances, not usePlateEditor or createPlateEditor from platejs/react.

Basic Usage

Provide a server-side editor instance and configure your Plate components during editor creation.

lib/generate-html.ts
import { createBaseEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static'; // Static import
// Import a server-safe registry kit (NOT from /react package paths)
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
// Import your STATIC components for rendering
import { ParagraphElementStatic } from '@/components/editor/paragraph-static';
import { HeadingElementStatic } from '@/components/editor/heading-static';
// For a styled static output, you might use a wrapper like EditorStatic
import { EditorStatic } from '@/components/editor/editor-static';
 
// Map plugin names to their STATIC rendering components
const components = {
  p: ParagraphElementStatic, // 'p' is the default name for paragraphs
  h1: HeadingElementStatic,
  // ... add mappings for all your elements and marks
};
 
// Create a server-side editor instance with components
const editor = createBaseEditor({
  plugins: [
    ...BaseBasicBlocksKit, // Paragraph, headings, blockquote, and horizontal rule
    // ... add all other base plugins relevant to your content
  ],
  components,
  initialValue: [
    { type: 'heading', level: 1, children: [{ text: 'My Title' }] },
    { type: 'paragraph', children: [{ text: 'My content.' }] },
  ],
});
 
async function getMyHtml() {
  const html = await renderStaticHtml(editor, {
    // Optional: Use a custom wrapper like EditorStatic for styling
    // editorComponent: EditorStatic,
    // props: { variant: 'none', className: 'p-4 m-4 border' },
  });
 
  return html;
}
lib/generate-html.ts
import { createBaseEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static'; // Static import
// Import a server-safe registry kit (NOT from /react package paths)
import { BaseBasicBlocksKit } from '@/components/editor/basic-blocks-static';
// Import your STATIC components for rendering
import { ParagraphElementStatic } from '@/components/editor/paragraph-static';
import { HeadingElementStatic } from '@/components/editor/heading-static';
// For a styled static output, you might use a wrapper like EditorStatic
import { EditorStatic } from '@/components/editor/editor-static';
 
// Map plugin names to their STATIC rendering components
const components = {
  p: ParagraphElementStatic, // 'p' is the default name for paragraphs
  h1: HeadingElementStatic,
  // ... add mappings for all your elements and marks























Styling Serialized HTML

renderStaticHtml returns only the HTML rendered for the editor content itself. If you use styled components such as EditorStatic or custom static components with classes, include their CSS wherever the HTML is displayed.

This often means wrapping the serialized HTML in a full HTML document that includes your stylesheets:

lib/generate-full-html-document.ts
// ... (previous setup from generate-html.ts)
 
async function getFullHtmlDocument() {
  const editorHtmlContent = await getMyHtml(); // From previous example
 
  const fullHtml = `<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="/path/to/your-global-styles.css" />
      <link rel="stylesheet" href="/path/to/tailwind-or-component-styles.css" />
      <title>Serialized Content</title>
    </head>
    <body>
      <div class="my-document-wrapper prose dark:prose-invert">
        ${editorHtmlContent}
      </div>
    </body>
  </html>`;
  return fullHtml;
Static Output Only

The serialization process converts Plate nodes to static HTML. Interactive features (React event handlers, client-side hooks) or components relying on browser APIs will not function in the serialized output.

Using Static Components

For server-side serialization, you must use static versions of your components (no client-only code, no React hooks like useEffect or useState).

Refer to the Static Rendering Guide for detailed instructions on creating server-safe static components for your Plate elements and marks.

components/editor/paragraph-static.tsx
import React from 'react';
import type { BaseParagraphPlugin } from 'platejs';
import type { PliteElementProps } from 'platejs/static';
 
// Example static paragraph component
export function ParagraphElementStatic(
  props: PliteElementProps<typeof BaseParagraphPlugin>
) {
  return (
    <PliteElement {...props} className={cn('m-0 px-0 py-1')}>
      {props.children}
    </PliteElement>
  );
}

HTML to Plate

The HTML decoder converts strings or DOM elements back into Plate content. It preserves structure, formatting, and attributes when the installed plugins own matching codecs.

Basic Usage

Call the root HTML API from a client-side Plate editor.

components/my-html-importer.tsx
import { usePlateEditor } from 'platejs/react';
// Import ALL Plate plugins needed to represent the HTML content
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... and so on for bold, italic, tables, lists, etc.
 
function MyHtmlImporter({ htmlString }: { htmlString: string }) {
  const editor = usePlateEditor({
    plugins: [
      ...BasicBlocksKit, // Paragraph, headings, blockquote, and horizontal rule
      // ... include all plugins corresponding to the HTML you expect to parse
    ],
  });
 
  const handleImport = () => {
    const value = editor.api.html.deserialize({ element: htmlString });
 
    if (!value) return;
 
    editor.update.value.replace({ children: value });
  };
 
  // ... render your editor and a button to trigger handleImport ...
  return <button onClick={handleImport}>Import HTML</button>;
}
components/my-html-importer.tsx
import { usePlateEditor } from 'platejs/react';
// Import ALL Plate plugins needed to represent the HTML content
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... and so on for bold, italic, tables, lists, etc.
 
function MyHtmlImporter({ htmlString }: { htmlString: string }) {
  const editor = usePlateEditor({
    plugins: [
      ...BasicBlocksKit, // Paragraph, headings, blockquote, and horizontal rule
      // ... include all plugins corresponding to the HTML you expect to parse
    ],
  });
 
  const handleImport = () => {
    const value = editor.api.html.deserialize({ element: htmlString });
 







Client-Side Operation

HTML deserialization through editor.api.html.deserialize is typically a client-side operation because it uses the compiled Plate plugin model.

Plugin HTML Codec Overview

Each Plate plugin owns the HTML tags, styles, and attributes for its schema claim. The same 'text/html' map returned by context-bound defineCodecs handles decode and encode.

HTML Element / StylePlate Plugin (Typical)Notes
<strong>, <b>, font-weight: 600,700,boldBoldPluginConverts to bold: true mark.
<em>, <i>, font-style: italicItalicPluginConverts to italic: true mark.
Plugin Configuration

Persisted element types come from the installed plugin schema handle (for example, editor.plugin(ParagraphPlugin).schema.type). The table shows typical associations. Include the corresponding Plate plugins for these rules to apply.

defineCodecs

Package authors declare node-level HTML meaning in the constructor's codecs callback before the app's terminal .configure() call. Destructure the context-bound defineCodecs and pass it the MIME-keyed map. This is the one inline inference anchor for the plugin schema and codec callbacks:

  • match is a non-empty array of tag, class, attribute, or style matchers.
  • decode returns only the value or properties owned by the plugin. Element codecs do not return type; Plate supplies the installed configured type.
  • encode returns a wrapper for a mark, a full node spec for an element, or an attribute/style patch for an element property.
  • Import-only mappings set decodeOnly: true instead of omitting encode silently.
  • priority resolves intentional overlap. Equal-priority exclusive claims fail model compilation instead of depending on plugin array order.
  • One plugin may provide a non-empty ordered rule tuple when it owns multiple HTML representations. Keep that tuple in the same codec map.

This element codec maps <aside> to a callout and preserves an app-configured storage type:

lib/callout-plugin.ts
import { property, schema } from 'platejs';
import { definePlatePlugin } from 'platejs/react';
 
const CalloutPlugin = definePlatePlugin('callout', {
  codecs: ({ defineCodecs }) =>
    defineCodecs({
      'text/html': {
        decode: ({ element }) => ({
          variant: element.dataset.variant || undefined,
        }),
        encode: ({ content, node }) => ({
          attributes: { 'data-variant': node.variant },
          children: content,
          tag: 












CalloutPlugin decodes <aside> as a callout element because the codec targets the installed descriptor. Define a separate named descriptor when a different persisted identity is required; .configure() does not rename one.

Use defineCodecs(map) for self and product codecs. A plugin contributing HTML behavior to another descriptor uses defineCodecs(TargetPlugin, map); the helper injects that target into every rule. Do not put target in the rule manually. One plugin may keep multiple HTML representations in a non-empty ordered 'text/html' tuple inside the same map.

Whole-Input HTML Hooks

Use query, transformData, and transformFragment on the plugin's 'text/html' codec for work that needs the complete incoming payload. These hooks run before or after node decoding; they do not declare node matches.

lib/html-cleanup-plugin.ts
const HtmlCleanupPlugin = definePlatePlugin('htmlCleanup', {
  codecs: ({ defineCodecs }) =>
    defineCodecs({
      'text/html': {
        query: ({ source }) => source.types.includes('text/html'),
        transformData: ({ data }) =>
          data.replaceAll(/<!--(?:Start|End)Fragment-->/g, ''),
      },
    }),
});

API Reference

renderStaticHtml(editor, options)

Converts Plate nodes from editor.children (or a provided value) into an HTML string. This function is typically used server-side.

Parameters

    A server-side Plate editor instance, created via createBaseEditor with components configured.

    Static React rendering options.

OptionsRenderStaticHtmlOptions<P = PlateStaticProps>

    A React component to wrap the entire editor content during static rendering. Defaults to PlateStatic. The component receives editor and any props passed here.

    Props to pass to the editorComponent. P defaults to PlateStaticProps.

    Class name prefixes to preserve when stripClassNames is true. Default preserve list in the stripping helper: ['plite-'].

    If true, removes all class names from the output HTML except those whose prefixes are listed in preserveClassNames. Default: false.

    If true, removes all data-* attributes from the output HTML. Default: false.

Returns

    A promise that resolves to the serialized HTML string.


editor.api.html.deserialize(options)

Parses an HTML string or HTMLElement into a Plate Value (an array of Descendant nodes). This is typically used on the client-side with a fully configured Plate editor.

Parameters

    Options for deserialization.

Options

    The HTML string or HTMLElement to deserialize.

    If true (default), collapses whitespace from text nodes similarly to how browsers treat whitespace in HTML. Set to false to preserve all whitespace. Default: true.

Returns

    The deserialized Plate Value, or null when the compiled HTML decoder rejects an invalid or unsupported result.

Next Steps

  • Explore the Static Rendering guide for creating server-safe components.
  • Review individual plugin documentation for specific HTML serialization/deserialization capabilities and default rules.
  • See the Server-Side HTML Generation Example.
import { BaseLinkKit } from './link-static';
import { BaseListKit } from './list-static';
import { MarkdownKit } from './markdown';
import { BaseMathKit } from './math-static';
import { BaseMediaKit } from './media-static';
import { BaseMentionKit } from './mention-static';
import { BaseSuggestionKit } from './suggestion-static';
import { BaseTableKit } from './table-static';
import { BaseTocKit } from './toc-static';
import { BaseToggleKit } from './toggle-static';
export const BaseEditorKit = [
...BaseBasicBlocksKit,
...BaseCodeBlockKit,
...BaseTableKit,
...BaseToggleKit,
...BaseTocKit,
...BaseMediaKit,
...BaseCalloutKit,
...BaseColumnKit,
...BaseMathKit,
...BaseDateKit,
...BaseLinkKit,
...BaseMentionKit,
...BaseFootnoteKit,
...BaseBasicMarksKit,
...BaseFontKit,
...BaseListKit,
...BaseAlignKit,
...BaseLineHeightKit,
...BaseCommentKit,
...BaseSuggestionKit,
...MarkdownKit,
] as const;
import { createBaseEditor } from 'platejs';
import { renderStaticHtml } from 'platejs/static';
import { BaseEditorKit } from '@/components/editor/plugins-static';
 
const editor = createBaseEditor({
  plugins: BaseEditorKit,
  initialValue: [
    { type: 'heading', level: 1, children: [{ text: 'Hello World' }] },
    { type: 'paragraph', children: [{ text: 'This content will be serialized to HTML.' }] },
  ],
});
 
// Serialize to HTML
const html = await renderStaticHtml(editor);
}
from
'@/components/editor/plate-to-html'
;
import { PlateToHtmlEditorKit } from '@/components/editor/plate-to-html-kit';
import { alignValue } from '@/registry/examples/values/align-value';
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
import { basicMarksValue } from '@/registry/examples/values/basic-marks-value';
import { columnValue } from '@/registry/examples/values/column-value';
import { dateValue } from '@/registry/examples/values/date-value';
import { discussionValue } from '@/registry/examples/values/discussion-value';
import { equationValue } from '@/registry/examples/values/equation-value';
import { fontValue } from '@/registry/examples/values/font-value';
import { indentValue } from '@/registry/examples/values/indent-value';
import { lineHeightValue } from '@/registry/examples/values/line-height-value';
import { linkValue } from '@/registry/examples/values/link-value';
import { listValue } from '@/registry/examples/values/list-value';
import { mediaValue } from '@/registry/examples/values/media-value';
import { mentionValue } from '@/registry/examples/values/mention-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
export const metadata: Metadata = {
title: 'Plate to HTML',
};
const getCachedTailwindCss = React.cache(async () => {
const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
return await fs.readFile(cssPath, 'utf-8');
});
const createHtmlDocument = ({
editorHtml,
katexCDN,
tailwindCss,
theme,
}: {
editorHtml: string;
tailwindCss: string;
katexCDN?: string;
theme?: string;
}) => `<!DOCTYPE html>
<html lang="en"${theme === 'dark' ? ' class="dark"' : ''}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<style>${tailwindCss}</style>
${katexCDN}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap"
rel="stylesheet"
/>
<style>
:root {
--font-sans: 'Inter', 'Inter Fallback';
--font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
}
</style>
</head>
<body>
${editorHtml}
</body>
</html>`;
const createValue = (): EditorDocumentValue => ({
children: [
...basicBlocksValue,
...basicMarksValue,
...tocPlaygroundValue,
...linkValue,
...tableValue,
...equationValue,
...columnValue,
...mentionValue,
...dateValue,
...fontValue,
...discussionValue,
...alignValue,
...lineHeightValue,
...indentValue,
...listValue,
...mediaValue.children,
],
});
export default async function PlateToHtmlBlock() {
const editor = createStaticEditor({
plugins: PlateToHtmlEditorKit,
initialValue: createValue(),
});
const tailwindCss = await getCachedTailwindCss();
const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
// const cookieStore = await cookies();
// const theme = cookieStore.get('theme')?.value;
const theme = 'light';
// Get the editor content HTML using EditorStatic
const editorHtml = await renderStaticHtml(editor, {
editorComponent: EditorStatic,
props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
});
// Create the full HTML document
const html = createHtmlDocument({
editorHtml,
katexCDN,
tailwindCss,
theme,
});
return (
<div className="grid grid-cols-3 px-4">
<div className="p-2">
<h3 className={headingVariants()}>Editor</h3>
<EditorClient value={createValue()} />
</div>
<div className="p-2">
<h3 className={headingVariants()}>EditorView</h3>
<EditorViewClient value={createValue()} />
</div>
<div className="relative p-2">
<h3 className={headingVariants()}>HTML Iframe</h3>
<ExportHtmlButton
className="absolute top-10 right-0"
html={html}
serverTheme={theme}
/>
<HtmlIframe
className="h-[7500px] w-full"
html={html}
serverTheme={theme}
/>
</div>
</div>
);
}
const headingVariants = cva(
'group mt-8 scroll-m-20 font-heading font-semibold text-xl tracking-tight'
);
import fs from 'node:fs/promises';
import path from 'node:path';
 
import { cva } from 'class-variance-authority';
import type { Metadata } from 'next';
import type { EditorDocumentValue } from 'platejs';
import { createStaticEditor, renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import { EditorStatic } from '@/components/editor/editor-static';
import {
  EditorClient,
  EditorViewClient,
  ExportHtmlButton,
  HtmlIframe,
} from '@/components/editor/plate-to-html';
import { PlateToHtmlEditorKit } from '@/components/editor/plate-to-html-kit';
import { alignValue } from '@/registry/examples/values/align-value';
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
import { basicMarksValue } from '@/registry/examples/values/basic-marks-value';
import { columnValue } from '@/registry/examples/values/column-value';
import { dateValue } from '@/registry/examples/values/date-value';
import { discussionValue } from '@/registry/examples/values/discussion-value';
import { equationValue } from '@/registry/examples/values/equation-value';
import { fontValue } from '@/registry/examples/values/font-value';
import { indentValue } from '@/registry/examples/values/indent-value';
import { lineHeightValue } from '@/registry/examples/values/line-height-value';
import { linkValue } from '@/registry/examples/values/link-value';
import { listValue } from '@/registry/examples/values/list-value';
import { mediaValue } from '@/registry/examples/values/media-value';
import { mentionValue } from '@/registry/examples/values/mention-value';
import { tableValue } from '@/registry/examples/values/table-value';
import { tocPlaygroundValue } from '@/registry/examples/values/toc-value';
 
export const metadata: Metadata = {
  title: 'Plate to HTML',
};
 
const getCachedTailwindCss = React.cache(async () => {
  const cssPath = path.join(process.cwd(), 'public', 'tailwind.css');
 
  return await fs.readFile(cssPath, 'utf-8');
});
 
const createHtmlDocument = ({
  editorHtml,
  katexCDN,
  tailwindCss,
  theme,
}: {
  editorHtml: string;
  tailwindCss: string;
  katexCDN?: string;
  theme?: string;
}) => `<!DOCTYPE html>
<html lang="en"${theme === 'dark' ? ' class="dark"' : ''}>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="color-scheme" content="light dark" />
    <style>${tailwindCss}</style>
    ${katexCDN}
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap"
      rel="stylesheet"
    />
    <style>
      :root {
        --font-sans: 'Inter', 'Inter Fallback';
        --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
      }
    </style>
  </head>
  <body>
    ${editorHtml}
  </body>
</html>`;
 
const createValue = (): EditorDocumentValue => ({
  children: [
    ...basicBlocksValue,
    ...basicMarksValue,
    ...tocPlaygroundValue,
    ...linkValue,
    ...tableValue,
    ...equationValue,
    ...columnValue,
    ...mentionValue,
    ...dateValue,
    ...fontValue,
    ...discussionValue,
    ...alignValue,
    ...lineHeightValue,
    ...indentValue,
    ...listValue,
    ...mediaValue.children,
  ],
});
 
export default async function PlateToHtmlBlock() {
  const editor = createStaticEditor({
    plugins: PlateToHtmlEditorKit,
    initialValue: createValue(),
  });
 
  const tailwindCss = await getCachedTailwindCss();
  const katexCDN = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
 
  // const cookieStore = await cookies();
  // const theme = cookieStore.get('theme')?.value;
  const theme = 'light';
 
  // Get the editor content HTML using EditorStatic
  const editorHtml = await renderStaticHtml(editor, {
    editorComponent: EditorStatic,
    props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
  });
 
  // Create the full HTML document
  const html = createHtmlDocument({
    editorHtml,
    katexCDN,
    tailwindCss,
    theme,
  });
 
  return (
    <div className="grid grid-cols-3 px-4">
      <div className="p-2">
        <h3 className={headingVariants()}>Editor</h3>
        <EditorClient value={createValue()} />
      </div>
 
      <div className="p-2">
        <h3 className={headingVariants()}>EditorView</h3>
        <EditorViewClient value={createValue()} />
      </div>
 
      <div className="relative p-2">
        <h3 className={headingVariants()}>HTML Iframe</h3>
        <ExportHtmlButton
          className="absolute top-10 right-0"
          html={html}
          serverTheme={theme}
        />
        <HtmlIframe
          className="h-[7500px] w-full"
          html={html}
          serverTheme={theme}
        />
      </div>
    </div>
  );
}
 
const headingVariants = cva(
  'group mt-8 scroll-m-20 font-heading font-semibold text-xl tracking-tight'
);
};
// Create a server-side editor instance with components
const editor = createBaseEditor({
plugins: [
...BaseBasicBlocksKit, // Paragraph, headings, blockquote, and horizontal rule
// ... add all other base plugins relevant to your content
],
components,
initialValue: [
{ type: 'heading', level: 1, children: [{ text: 'My Title' }] },
{ type: 'paragraph', children: [{ text: 'My content.' }] },
],
});
async function getMyHtml() {
const html = await renderStaticHtml(editor, {
// Optional: Use a custom wrapper like EditorStatic for styling
// editorComponent: EditorStatic,
// props: { variant: 'none', className: 'p-4 m-4 border' },
});
return html;
}
}
lib/generate-full-html-document.ts
// ... (previous setup from generate-html.ts)
 
async function getFullHtmlDocument() {
  const editorHtmlContent = await getMyHtml(); // From previous example
 
  const fullHtml = `<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="/path/to/your-global-styles.css" />
      <link rel="stylesheet" href="/path/to/tailwind-or-component-styles.css" />
      <title>Serialized Content</title>
    </head>
    <body>
      <div class="my-document-wrapper prose dark:prose-invert">
        ${editorHtmlContent}
      </div>
    </body>
  </html>`;
  return fullHtml;
}
components/editor/paragraph-static.tsx
import React from 'react';
import type { BaseParagraphPlugin } from 'platejs';
import type { PliteElementProps } from 'platejs/static';
 
// Example static paragraph component
export function ParagraphElementStatic(
  props: PliteElementProps<typeof BaseParagraphPlugin>
) {
  return (
    <PliteElement {...props} className={cn('m-0 px-0 py-1')}>
      {props.children}
    </PliteElement>
  );
}
if (!value) return;
editor.update.value.replace({ children: value });
};
// ... render your editor and a button to trigger handleImport ...
return <button onClick={handleImport}>Import HTML</button>;
}
<u>, text-decoration: underline
UnderlinePlugin
Converts to underline: true mark.
<s>, <del>, <strike>, text-decoration: line-throughStrikethroughPluginConverts to strikethrough: true mark.
<sub>, vertical-align: subScriptPluginConverts to script: 'sub'.
<sup>, vertical-align: superScriptPluginConverts to script: 'sup'.
<code> (not in <pre>), font-family: ConsolasCodePluginConverts to code: true mark (inline code).
<kbd>KbdPluginConverts to kbd: true mark.
<p>ParagraphPluginConverts to paragraph element.
<h1> - <h6>HeadingPlugin–HeadingPluginConverts to corresponding heading elements (h1 - h6).
<ul>, <ol>, <li>ListPluginConverts list items to blocks with indent and listStyle properties.
<blockquote>BlockquotePluginConverts to blockquote element.
<pre> (often with <code> inside)CodeBlockPluginConverts to codeBlock element. Content split into codeLine.
<hr>HorizontalRulePluginConverts to horizontal rule element.
<a>LinkPluginConverts to link with a url property.
<img>ImagePluginConverts to image with a url property.
<iframe>MediaEmbedPluginConverts to media embed element, attempting to parse URL.
<table>TablePluginConverts to table element.
<tr>TablePluginConverts to tableRow.
<td>TablePluginConverts to tableCell.
<th>TablePluginConverts to tableCell with header: true.
style="background-color: ..."FontBackgroundColorPluginConverts to backgroundColor mark.
style="color: ..."FontColorPluginConverts to color mark.
style="font-family: ..."FontFamilyPluginConverts to fontFamily mark.
style="font-size: ..."FontSizePluginConverts to fontSize mark.
style="font-weight: ..." (other than bold values)FontWeightPluginConverts to fontWeight mark for non-standard bold values.
<mark>HighlightPluginConverts to highlight: true mark.
style="text-align: ..."TextAlignPluginSets textAlign property on block elements.
style="line-height: ..."LineHeightPluginSets lineHeight property on block elements.
'aside'
,
}),
match: [{ tag: 'aside' }],
},
}),
schema: {
element: {
content: schema.content.text({ default: 'text', min: 1 }),
properties: {
variant: property.string(),
},
},
},
});
lib/callout-plugin.ts
import { property, schema } from 'platejs';
import { definePlatePlugin } from 'platejs/react';
 
const CalloutPlugin = definePlatePlugin('callout', {
  codecs: ({ defineCodecs }) =>
    defineCodecs({
      'text/html': {
        decode: ({ element }) => ({
          variant: element.dataset.variant || undefined,
        }),
        encode: ({ content, node }) => ({
          attributes: { 'data-variant': node.variant },
          children: content,
          tag: 'aside',
        }),
        match: [{ tag: 'aside' }],
      },
    }),
  schema: {
    element: {
      content: schema.content.text({ default: 'text', min: 1 }),
      properties: {
        variant: property.string(),
      },
    },
  },
});
lib/html-cleanup-plugin.ts
const HtmlCleanupPlugin = definePlatePlugin('htmlCleanup', {
  codecs: ({ defineCodecs }) =>
    defineCodecs({
      'text/html': {
        query: ({ source }) => source.types.includes('text/html'),
        transformData: ({ data }) =>
          data.replaceAll(/<!--(?:Start|End)Fragment-->/g, ''),
      },
    }),
});