From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Examples
    • Plate to HTML
    • Export
    • Server-Side
    • Version History
    • Editable Voids
    • Huge Document
    • Hundreds Editors
    • Markdown Streaming
    • Preview Markdown
    • Collaboration Demo
    • Table Nomerge Demo
    • Excalidraw Demo
    • Code Drawing Demo
    • Single Block Demo
    • List Classic Demo
    • Find Replace Demo
    • AI Demo
    • Align Demo
    • Autoformat Demo
    • Basic Nodes Demo
    • Block Menu Demo
    • Node Selection Demo
    • Column Demo
    • Code Block Demo
    • Callout Demo
    • Discussion Demo
    • Cursor Overlay Demo
    • Date Demo
    • Footnote Demo
    • Drag & Drop Demo
    • Emoji Demo
    • Equation Demo
    • Exit Break Demo
    • Floating Toolbar Demo
    • Font Demo
    • Indent Demo
    • List Demo
    • Line Height Demo
    • Link Demo
    • Media Demo
    • Mention Demo
    • Block Placeholder Demo
    • Serializing CSV Demo
    • Serializing Docx Demo
    • Serializing HTML Demo
    • Serializing Markdown Demo
    • Slash Command Demo
    • Plugin Rules Demo
    • Table Demo
    • Table of Contents Demo
    • Toggle Demo

Export

PreviousNext

Export a Plate document to HTML, PDF, image, Markdown, or Word.

Plus

This example exports the current Plate editor value from the browser. The registry export-toolbar-button owns the client-side download menu for HTML, PDF, image, Markdown, and Word output.

Demo

Loading…
Plate to HTMLServer-Side

On This Page

DemoToolbar SourceExport FormatsDOCX Static KitPlus ExportRelated
Build your editor
Production-ready AI template and reusable components.
Get all-access

Toolbar Source

Install the Export Toolbar Button component to add the menu to an editor toolbar.

'use client';
 
import { exportToDocx } from '@platejs/docx-export';
import { MarkdownPlugin } from '@platejs/markdown';
import { ArrowDownToLineIcon } from 'lucide-react';
import { createBaseEditor } from 'platejs';
import { useEditor } from 'platejs/react';
import { renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { DocxExportKit } from '@/components/editor/docx-export';
import { BaseEditorKit } from '@/components/editor/plugins-static';
import { ToolbarButton } from '@/components/editor/toolbar';
 
import { EditorStatic } from './editor-static';
 
const siteUrl = 'https://platejs.org';
 
const downloadFile = async (url: string, filename: string) => {
  const response = await fetch(url);
  const blob = await response.blob();
  const blobUrl = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
 
  link.href = blobUrl;
  link.download = filename;
  document.body.append(link);
  link.click();
  link.remove();
  window.URL.revokeObjectURL(blobUrl);
};
 
export function ExportToolbarButton() {
  const editor = useEditor();
  const [open, setOpen] = React.useState(false);
 
  const getCanvas = async () => {
    const { default: html2canvas } = await import('html2canvas-pro');
 
    const style = document.createElement('style');
    document.head.append(style);
    const editorElement = editor.api.dom.resolveDOMNode(editor);
 
    if (!editorElement) {
      style.remove();
      throw new Error('Cannot resolve editor DOM node for export.');
    }
 
    const canvas = await html2canvas(editorElement, {
      onclone: (document: Document) => {
        const innerEditorElement = document.querySelector(
          '[contenteditable="true"]'
        );
        if (innerEditorElement) {
          Array.from(innerEditorElement.querySelectorAll('*')).forEach(
            (element) => {
              const existingStyle = element.getAttribute('style') || '';
              element.setAttribute(
                'style',
                `${existingStyle}; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important`
              );
            }
          );
        }
      },
    });
    style.remove();
 
    return canvas;
  };
 
  const exportToPdf = async () => {
    const [canvas, PDFLib] = await Promise.all([
      getCanvas(),
      import('pdf-lib'),
    ]);
    const pdfDoc = await PDFLib.PDFDocument.create();
    const page = pdfDoc.addPage([canvas.width, canvas.height]);
    const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));
    const { height, width } = imageEmbed.scale(1);
    page.drawImage(imageEmbed, {
      height,
      width,
      x: 0,
      y: 0,
    });
    const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
 
    await downloadFile(pdfBase64, 'plate.pdf');
  };
 
  const exportToImage = async () => {
    const canvas = await getCanvas();
    await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
  };
 
  const exportToHtml = async () => {
    const editorStatic = createBaseEditor({
      plugins: BaseEditorKit,
      initialValue: editor.read.children(),
    });
 
    const editorHtml = await renderStaticHtml(editorStatic, {
      editorComponent: EditorStatic,
      props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
    });
 
    const tailwindCss = `<link rel="stylesheet" href="${siteUrl}/tailwind.css">`;
    const katexCss = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
 
    const html = `<!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="color-scheme" content="light dark" />
        <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"
        />
        ${tailwindCss}
        ${katexCss}
        <style>
          :root {
            --font-sans: 'Inter', 'Inter Fallback';
            --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
          }
        </style>
      </head>
      <body>
        ${editorHtml}
      </body>
    </html>`;
 
    const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
 
    await downloadFile(url, 'plate.html');
  };
 
  const exportToMarkdown = async () => {
    const md = editor.plugin(MarkdownPlugin).api.serialize();
    const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
    await downloadFile(url, 'plate.md');
  };
 
  const exportToWord = async () => {
    const blob = await exportToDocx(editor.read.value().children, {
      editorPlugins: [...BaseEditorKit, ...DocxExportKit],
    });
 
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'plate.docx';
    document.body.append(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(url);
  };
 
  return (
    <DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
      <DropdownMenuTrigger asChild>
        <ToolbarButton pressed={open} tooltip="Export" isDropdown>
          <ArrowDownToLineIcon className="size-4" />
        </ToolbarButton>
      </DropdownMenuTrigger>
 
      <DropdownMenuContent align="start">
        <DropdownMenuGroup>
          <DropdownMenuItem onSelect={exportToHtml}>
            Export as HTML
          </DropdownMenuItem>
          <DropdownMenuItem onSelect={exportToPdf}>
            Export as PDF
          </DropdownMenuItem>
          <DropdownMenuItem onSelect={exportToImage}>
            Export as Image
          </DropdownMenuItem>
          <DropdownMenuItem onSelect={exportToMarkdown}>
            Export as Markdown
          </DropdownMenuItem>
          <DropdownMenuItem onSelect={exportToWord}>
            Export as Word
          </DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
'use client';
 
import { exportToDocx } from '@platejs/docx-export';
import { MarkdownPlugin } from '@platejs/markdown';
import { ArrowDownToLineIcon } from 'lucide-react';
import { createBaseEditor } from 'platejs';
import { useEditor } from 'platejs/react';
import { renderStaticHtml } from 'platejs/static';
import * as React from 'react';
 
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';





















































































































































































Export Formats

FormatImplementationOutput
HTMLrenderStaticHtml from platejs/static with BaseEditorKit and EditorStaticplate.html
PDFhtml2canvas-pro snapshot converted with pdf-libplate.pdf
Imagehtml2canvas-pro snapshotplate.png
Markdowneditor.api.markdown.serialize() from MarkdownPluginplate.md
WordexportToDocx(editor.read.children(), { editorPlugins }) from @platejs/docx-exportplate.docx

The PDF and image exporters snapshot the current editable DOM. Use them for a quick client-side download, not for paginated print layout.

DOCX Static Kit

Word export passes the base static editor kit plus DocxExportKit so custom nodes can render with DOCX-friendly components.

import { BaseHeadingPlugin } from '@platejs/basic-nodes';
import { BaseCalloutPlugin } from '@platejs/callout';
import {
  BaseCodeBlockPlugin,
  BaseCodeHighlightPlugin,
  BaseCodeLinePlugin,
} from '@platejs/code-block';
import { BaseColumnItemPlugin, BaseColumnPlugin } from '@platejs/layout';
import { BaseEquationPlugin, BaseInlineEquationPlugin } from '@platejs/math';
import { BaseTocPlugin } from '@platejs/toc';
 
import { CalloutElementDocx } from '@/components/editor/callout-static';
import {
  CodeBlockElementDocx,
  CodeLineElementDocx,
  CodeSyntaxLeafDocx,
} from '@/components/editor/code-block-static';
import {
  ColumnElementDocx,
  ColumnGroupElementDocx,
} from '@/components/editor/column-static';
import { HeadingElementDocx } from '@/components/editor/heading-static';
import {
  EquationElementDocx,
  InlineEquationElementDocx,
} from '@/components/editor/math-static';
import { TocElementDocx } from '@/components/editor/toc-static';
 
/**
 * Editor kit for DOCX export.
 *
 * Uses standard static components for most elements (with juice CSS inlining),
 * but uses docx-specific components for elements that need special handling:
 * - Code blocks (syntax highlighting, line breaks)
 * - Columns (table layout instead of flexbox)
 * - Equations (inline font instead of KaTeX)
 * - Callouts (table layout for icon placement)
 * - Headings (bookmark anchors for TOC links)
 * - TOC (anchor links with paragraph breaks)
 *
 * Tables use base version with juice CSS inlining.
 */
export const DocxExportKit = [
  BaseCodeBlockPlugin.configure({
    component: CodeBlockElementDocx,
  }),
  BaseCodeLinePlugin.configure({
    component: CodeLineElementDocx,
  }),
  BaseCodeHighlightPlugin.configure({
    component: CodeSyntaxLeafDocx,
  }),
  BaseColumnItemPlugin.configure({
    component: ColumnElementDocx,
  }),
  BaseColumnPlugin.configure({
    component: ColumnGroupElementDocx,
  }),
  BaseEquationPlugin.configure({
    component: EquationElementDocx,
  }),
  BaseInlineEquationPlugin.configure({
    component: InlineEquationElementDocx,
  }),
  BaseCalloutPlugin.configure({
    component: CalloutElementDocx,
  }),
  BaseHeadingPlugin.configure({
    component: HeadingElementDocx,
  }),
  BaseTocPlugin.configure({
    component: TocElementDocx,
  }),
];
import { BaseHeadingPlugin } from '@platejs/basic-nodes';
import { BaseCalloutPlugin } from '@platejs/callout';
import {
  BaseCodeBlockPlugin,
  BaseCodeHighlightPlugin,
  BaseCodeLinePlugin,
} from '@platejs/code-block';
import { BaseColumnItemPlugin, BaseColumnPlugin } from '@platejs/layout';
import { BaseEquationPlugin, BaseInlineEquationPlugin } from '@platejs/math';
import { BaseTocPlugin } from '@platejs/toc';
 
import { CalloutElementDocx } from '@/components/editor/callout-static';
import {
  CodeBlockElementDocx,
  CodeLineElementDocx,
  CodeSyntaxLeafDocx,
} from '@/components/editor/code-block-static'
























































DocxExportKit overrides code blocks, columns, equations, callouts, and table of contents rendering for the DOCX conversion path.

Plus Export

Plate Plus includes a server-side export flow for PDF output with page settings.

Get the code

Related

  • DOCX covers @platejs/docx-export options and plugin APIs.
  • Markdown covers Markdown serialization.
  • Static Rendering covers static editor rendering and HTML serialization.
import { DocxExportKit } from '@/components/editor/docx-export';
import { BaseEditorKit } from '@/components/editor/plugins-static';
import { ToolbarButton } from '@/components/editor/toolbar';
import { EditorStatic } from './editor-static';
const siteUrl = 'https://platejs.org';
const downloadFile = async (url: string, filename: string) => {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.append(link);
link.click();
link.remove();
window.URL.revokeObjectURL(blobUrl);
};
export function ExportToolbarButton() {
const editor = useEditor();
const [open, setOpen] = React.useState(false);
const getCanvas = async () => {
const { default: html2canvas } = await import('html2canvas-pro');
const style = document.createElement('style');
document.head.append(style);
const editorElement = editor.api.dom.resolveDOMNode(editor);
if (!editorElement) {
style.remove();
throw new Error('Cannot resolve editor DOM node for export.');
}
const canvas = await html2canvas(editorElement, {
onclone: (document: Document) => {
const innerEditorElement = document.querySelector(
'[contenteditable="true"]'
);
if (innerEditorElement) {
Array.from(innerEditorElement.querySelectorAll('*')).forEach(
(element) => {
const existingStyle = element.getAttribute('style') || '';
element.setAttribute(
'style',
`${existingStyle}; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important`
);
}
);
}
},
});
style.remove();
return canvas;
};
const exportToPdf = async () => {
const [canvas, PDFLib] = await Promise.all([
getCanvas(),
import('pdf-lib'),
]);
const pdfDoc = await PDFLib.PDFDocument.create();
const page = pdfDoc.addPage([canvas.width, canvas.height]);
const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));
const { height, width } = imageEmbed.scale(1);
page.drawImage(imageEmbed, {
height,
width,
x: 0,
y: 0,
});
const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
await downloadFile(pdfBase64, 'plate.pdf');
};
const exportToImage = async () => {
const canvas = await getCanvas();
await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
};
const exportToHtml = async () => {
const editorStatic = createBaseEditor({
plugins: BaseEditorKit,
initialValue: editor.read.children(),
});
const editorHtml = await renderStaticHtml(editorStatic, {
editorComponent: EditorStatic,
props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
});
const tailwindCss = `<link rel="stylesheet" href="${siteUrl}/tailwind.css">`;
const katexCss = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<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"
/>
${tailwindCss}
${katexCss}
<style>
:root {
--font-sans: 'Inter', 'Inter Fallback';
--font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
}
</style>
</head>
<body>
${editorHtml}
</body>
</html>`;
const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
await downloadFile(url, 'plate.html');
};
const exportToMarkdown = async () => {
const md = editor.plugin(MarkdownPlugin).api.serialize();
const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
await downloadFile(url, 'plate.md');
};
const exportToWord = async () => {
const blob = await exportToDocx(editor.read.value().children, {
editorPlugins: [...BaseEditorKit, ...DocxExportKit],
});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'plate.docx';
document.body.append(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
};
return (
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
<DropdownMenuTrigger asChild>
<ToolbarButton pressed={open} tooltip="Export" isDropdown>
<ArrowDownToLineIcon className="size-4" />
</ToolbarButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuGroup>
<DropdownMenuItem onSelect={exportToHtml}>
Export as HTML
</DropdownMenuItem>
<DropdownMenuItem onSelect={exportToPdf}>
Export as PDF
</DropdownMenuItem>
<DropdownMenuItem onSelect={exportToImage}>
Export as Image
</DropdownMenuItem>
<DropdownMenuItem onSelect={exportToMarkdown}>
Export as Markdown
</DropdownMenuItem>
<DropdownMenuItem onSelect={exportToWord}>
Export as Word
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
;
import {
ColumnElementDocx,
ColumnGroupElementDocx,
} from '@/components/editor/column-static';
import { HeadingElementDocx } from '@/components/editor/heading-static';
import {
EquationElementDocx,
InlineEquationElementDocx,
} from '@/components/editor/math-static';
import { TocElementDocx } from '@/components/editor/toc-static';
/**
* Editor kit for DOCX export.
*
* Uses standard static components for most elements (with juice CSS inlining),
* but uses docx-specific components for elements that need special handling:
* - Code blocks (syntax highlighting, line breaks)
* - Columns (table layout instead of flexbox)
* - Equations (inline font instead of KaTeX)
* - Callouts (table layout for icon placement)
* - Headings (bookmark anchors for TOC links)
* - TOC (anchor links with paragraph breaks)
*
* Tables use base version with juice CSS inlining.
*/
export const DocxExportKit = [
BaseCodeBlockPlugin.configure({
component: CodeBlockElementDocx,
}),
BaseCodeLinePlugin.configure({
component: CodeLineElementDocx,
}),
BaseCodeHighlightPlugin.configure({
component: CodeSyntaxLeafDocx,
}),
BaseColumnItemPlugin.configure({
component: ColumnElementDocx,
}),
BaseColumnPlugin.configure({
component: ColumnGroupElementDocx,
}),
BaseEquationPlugin.configure({
component: EquationElementDocx,
}),
BaseInlineEquationPlugin.configure({
component: InlineEquationElementDocx,
}),
BaseCalloutPlugin.configure({
component: CalloutElementDocx,
}),
BaseHeadingPlugin.configure({
component: HeadingElementDocx,
}),
BaseTocPlugin.configure({
component: TocElementDocx,
}),
];