Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Comments
  • Discussion
  • Suggestions
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Details
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Drag & Drop
    • Navigation Feedback
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • CSV
    • DOCX
    • HTML
    • Markdown

DOCX

PreviousNext

Paste Word content, import bounded DOCX files, and export an explicit document projection.

Use WordPastePlugin for clipboard content. Use importDocx and exportDocx for files. The three operations have separate entrypoints and dependencies.

Loading…
CSVComponents

On This Page

FeaturesKit usageInstallationAdd kitPaste from WordImport a DOCX fileRetain the source for later exportImport commentsImport tracked revisionsExport a DOCX fileContent lossLinks and imagesSource-aware exportExport commentsStatic rendering and stylesFormat supportAPI referenceEntrypointsimportDocxDocxSourceexportDocx
Build your editor
Production-ready AI template and reusable components.
Get all-access

Features

  • Normalize HTML and RTF pasted from Microsoft Word
  • Import a bounded DOCX package into one complete editor document
  • Preserve supported Word revisions and comment ranges as structured data
  • Export one captured editor snapshot as an accepted, proposed, or review DOCX
Report an issue
Client boundary

Paste, file import, and file download use browser APIs. Keep these operations in client code. Export uses React static rendering, so pass the descriptors and static components that define your document output.

Kit usage

The app-local DocxKit installs WordPastePlugin. File import and export are standalone functions.

Installation

pnpm add platejs @tanstack/react-virtual juice validator
pnpm add platejs @tanstack/react-virtual juice validator

Add kit

'use client';
 
import { WordPastePlugin } from 'platejs/docx/paste';
 
export const DocxKit = [WordPastePlugin] as const;
'use client';
 
import { WordPastePlugin } from 'platejs/docx/paste';
 
export const DocxKit = [WordPastePlugin] as const;
components/editor/editor.tsx
import { createEditor } from "platejs/react";
import { DocxKit } from "@/components/editor/docx";
 
const editor = createEditor({
  plugins: [...DocxKit],
});
components/editor/editor.tsx
import { createEditor } from "platejs/react";
import { DocxKit } from "@/components/editor/docx";
 
const editor = createEditor({

Paste from Word

WordPastePlugin inlines pasted CSS and normalizes Word HTML and RTF before the installed Plate format mappings decode the content.

components/editor/editor.tsx
import { WordPastePlugin } from "platejs/docx/paste";
import { createEditor } from "platejs/react";
 
const editor = createEditor({
  plugins: [WordPastePlugin],
});
components/editor/editor.tsx
import { WordPastePlugin } from "platejs/docx/paste";
import { createEditor } from "platejs/react";
 
const editor = createEditor({
  plugins: [WordPastePlugin],
});

Clipboard paste does not upload embedded images or preserve Word table geometry. Configure those destination policies in their owning media and table features.

Import a DOCX file

importDocx reads a Blob or ArrayBuffer without modifying the editor. On success, replace the complete editor value so document metadata, named roots, and authored review state stay coherent.

Inside a mounted React control, get that complete editor with useModelEditor(). useEditor() returns the provider-selected mounted command view, whose root and authored projection belong to that view.

pnpm add platejs @tanstack/react-virtual mammoth validator
pnpm add platejs @tanstack/react-virtual mammoth validator
components/editor/import-docx.tsx
import { importDocx } from "platejs/docx/import";
import { createEditor } from "platejs/react";
import { BaseEditorKit } from "@/components/editor/plugins-static";
 
const editor = createEditor({
  plugins: BaseEditorKit,
});
 
export async function importFile(file: File) {
  const result = await importDocx(file, {
    plugins: BaseEditorKit,
  });
 
  if (!result.ok) {
    return { diagnostics: result.diagnostics };
  }
 
  editor.update.value.replace(result.document);
 
  return {
    comments: result.comments,
    diagnostics: result.diagnostics,
  };
}
components/editor/import-docx.tsx
import { importDocx } from "platejs/docx/import";
import { createEditor } from "platejs/react";
import { BaseEditorKit } from "@/components/editor/plugins-static";
 
const editor = createEditor({
  plugins: BaseEditorKit,
});
 
export async function importFile(file: File) {
  const result = await importDocx(file, {
    plugins: BaseEditorKit,
  });
 
  if (!result.ok) {
    return { diagnostics: result.diagnostics };
  }
 
  editor.update.value.replace(result.document);





An expected file, package, limit, or decode failure returns { ok: false, diagnostics } and no document. An aborted import rejects with the signal reason. Invalid options and internal invariant failures throw.

Retain the source for later export

Set retainSource: true when the same editor imports, edits, and exports a Word file. A successful import then includes source. It is a DocxSource when exact export can return the package unchanged. Otherwise it is null, with a source-unavailable warning that names the first part exact export cannot admit. Keep the source in application state, pass it to exportDocx, and dispose it when the document closes or another file replaces it.

components/editor/docx-roundtrip.tsx
import { exportDocx } from "platejs/docx/export";
import { importDocx } from "platejs/docx/import";
 
const imported = await importDocx(file, {
  plugins: BaseEditorKit,
  retainSource: true,
});
 
if (!imported.ok) return imported.diagnostics;
 
editor.update.value.replace(imported.document);
 
const exported = await exportDocx(editor, {
  projection: "review",
  source: imported.source,
  stylesheet,
});
 
imported.source?.dispose();
components/editor/docx-roundtrip.tsx
import { exportDocx } from "platejs/docx/export";
import { importDocx } from "platejs/docx/import";
 
const imported = await importDocx(file, {
  plugins: BaseEditorKit,
  retainSource: true,
});
 
if (!imported.ok) return imported.diagnostics;
 
editor.update.value.replace(imported.document);
 
const exported = await exportDocx(editor, {
  projection: "review",
  source: imported.source,
  stylesheet,
});
 
imported.source?.dispose();

Exact export admits a closed set of passive package content:

  • the main document at word/document.xml, styles, settings, numbering, fonts, and theme;
  • headers, footers, footnotes, endnotes, and comments;
  • core, extended, and custom document properties and a thumbnail;
  • AVIF, BMP, GIF, JPEG, PNG, and WebP images reached through internal relationships;
  • hyperlinks whose destination is absolute and meets the navigation floor.

PAGE is the only recognized field instruction. Any other part, relationship, markup namespace, field instruction, or XML processing instruction leaves source null. That includes unsafe or relative hyperlinks, other external relationships, macros, ActiveX controls, OLE objects, imported HTML chunks, external templates, custom XML data, and SVG or EMF images. A null source does not mean the import lost content; exporting without a source generates the document from editor content.

DocxSource retains the admitted compressed package plus immutable document, comment, schema, and limit correspondence. It does not retain expanded ZIP entries or an editor reference. dispose() is idempotent. An export that already acquired the source can finish after disposal; later exports report source-unavailable and generate a fresh DOCX.

Import comments

The importer returns comment format facts without writing app comment storage. Each DocxComment can include a rich Plate body, an exact target.range in proposed-projection coordinates, Word author and date metadata, a durable ID, a parent ID, and resolved state. Map those records to your app identities explicitly.

Import tracked revisions

Supported Word insertions, deletions, moves, run property changes, and paragraph property changes become authored changes. Revision IDs follow Word source order and structural dependencies; dates remain metadata. A missing or invalid date uses the authored unknown-time value and produces a diagnostic.

If a revision cannot map to the installed schema, the importer keeps the Word-visible proposed content and reports the loss in diagnostics.

Export a DOCX file

exportDocx captures the editor once before asynchronous rendering. Pass the visible projection explicitly.

pnpm add platejs @tanstack/react-virtual color-name html-to-vdom jszip juice mime-types virtual-dom xmlbuilder2
pnpm add platejs @tanstack/react-virtual color-name html-to-vdom jszip juice mime-types virtual-dom xmlbuilder2
components/editor/export-docx.tsx
import { exportDocx } from "platejs/docx/export";
import { createEditor } from "platejs/react";
import { BaseEditorKit } from "@/components/editor/plugins-static";
import { DOCX_EXPORT_STYLES } from "@/components/editor/docx-export";
 
const editor = createEditor({ plugins: BaseEditorKit });
 
export async function downloadEditorDocx() {
  const result = await exportDocx(editor, {
    orientation: "portrait",
    projection: "proposed",
    stylesheet: DOCX_EXPORT_STYLES,
    title: "Document",
  });
 
  if (!result.ok) return result.diagnostics;
 
  const url = URL.createObjectURL(result.blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = "document.docx";
  document.body.append(link);
  link.click();
  link.remove();
  URL.revokeObjectURL(url);
 
  return result.diagnostics;
}
components/editor/export-docx.tsx
import { exportDocx } from "platejs/docx/export";
import { createEditor } from "platejs/react";
import { BaseEditorKit } from "@/components/editor/plugins-static";
import { DOCX_EXPORT_STYLES } from "@/components/editor/docx-export";
 
const editor = createEditor({ plugins: BaseEditorKit });
 
export async function downloadEditorDocx() {
  const result = await exportDocx(editor, {
    orientation: "portrait",
    projection: "proposed",
    stylesheet: DOCX_EXPORT_STYLES,
    title: "Document",
  });
 
  if











Choose accepted to omit pending changes, proposed to apply them, or review to write Word tracked-change markup. Accepted and proposed exports warn about omitted pending changes and conflicts resolved to one side. A review export with any conflict returns ok: false and an authored-conflict error because Word revisions cannot represent that state faithfully.

Visible Word output omits Plate-only document state by default. Set nativeState: "attach" on a review export only when the file must carry a correspondence-bound Plate envelope for an exact trusted round trip:

const result = await exportDocx(editor, {
  nativeState: "attach",
  projection: "review",
});
const result = await exportDocx(editor, {
  nativeState: "attach",
  projection: "review",
});

The importer always converts the Word-visible package before considering an attached envelope. It restores native metadata and named roots only when the envelope version, installed schema, package-part digests, and accepted/proposed Word projections all match. An edited or corrupt package falls back to the Word-derived document with a native-data-ignored diagnostic.

Content loss

exportDocx rejects content loss by default. When an image cannot be embedded, or a supplied comment has no representable range, the result is ok: false with the loss as an error diagnostic. Set lossPolicy: "allow" to download the file and receive the same loss as warnings:

const result = await exportDocx(editor, {
  comments,
  lossPolicy: "allow",
  projection: "review",
});
const result = await exportDocx(editor, {
  comments,
  lossPolicy: "allow",
  projection: "review",
});

Named roots, document metadata, removed link destinations, and other properties DOCX cannot represent are warnings under both policies. An accepted export omits a comment whose annotated content that projection excludes, and reports a warning, as Word does when the annotated suggestion is rejected.

Links and images

Export writes a link destination only when it is absolute and meets the navigation floor, such as https: or mailto:. It writes bookmark links such as #introduction as Word anchors. An unsafe destination, or a relative link that has no document base in a Word file, keeps its text without the destination.

Images are embedded as PNG, JPEG, GIF, or BMP. Set allowRemoteImages: true to fetch HTTP(S) images during export. These images are omitted and leave their alt text:

  • an image with an unsafe, relative, or blob source;
  • a remote image when allowRemoteImages is off;
  • an image that cannot be fetched;
  • an image in any other format.

Before returning a file, export checks the written package against the same passive vocabulary that retained sources use. Output outside it is withheld with an invalid-package error.

Source-aware export

With a valid source, an unchanged review export returns the admitted file byte for byte. Exact reuse requires the same compiled schema, an unchanged review document, unchanged or omitted comments, and no title, margins, orientation, or pageSize override. Static renderer options are bypassed on this path because no content is regenerated.

If an exact check fails, exportDocx regenerates the complete document body. It keeps these source units when their internal relationships are closed, reachable, and nonconflicting:

  • headers and footers from a document with one section, including their images and hyperlinks;
  • package-level subgraphs reached from the root relationships file.

The regenerated package omits source body XML, body-only parts such as footnotes and body images, and headers and footers from a document with multiple sections. Diagnostics report why the source was rewritten and identify each omitted source part.

When regeneration is required, omitted comments means that no comments are generated. Pass the current app-owned comment set explicitly to retain comments after an edit. Imported comment ranges are not relocated automatically.

Export comments

Pass comments whose ranges address the editor's proposed projection. Export maps those ranges into accepted or review output before rendering:

const result = await exportDocx(editor, {
  comments,
  projection: "review",
});
const result = await exportDocx(editor, {
  comments,
  projection: "review",
});

The same configured static renderer handles the main document and rich comment bodies. Under the default lossPolicy, a comment without a complete range fails the export; with lossPolicy: "allow" the export omits it with a warning.

Static rendering and styles

DOCX owns its semantic static mappings for code blocks, columns, equations, callouts, headings, and tables of contents. The app supplies presentation CSS and may pass a custom root component when it needs a different wrapper. DOCX_EXPORT_STYLES is the registry's copied presentation preset.

export const DOCX_EXPORT_STYLES = `
body {
  font-family: 'Calibri', 'Arial', sans-serif;
  font-size: 11pt;
  line-height: 1.5;
  color: #000;
  margin: 0;
  padding: 20px;
}
h1 { font-size: 24pt; font-weight: bold; margin: 0 0 12pt 0; }
h2 { font-size: 18pt; font-weight: bold; margin: 0 0 10pt 0; }
h3 { font-size: 14pt; font-weight: bold; margin: 0 0 8pt 0; }
h4 { font-size: 12pt; font-weight: bold; margin: 0 0 6pt 0; }
h5 { font-size: 11pt; font-weight: bold; margin: 0 0 6pt 0; }
h6 { font-size: 10pt; font-weight: bold; margin: 0 0 6pt 0; }
p { margin: 0 0 8pt 0; }
ul, ol { margin: 0 0 8pt 0; padding-left: 20pt; }
li { margin: 0 0 4pt 0; }
strong, b { font-weight: bold; }
em, i { font-style: italic; }
u { text-decoration: underline; }
s, strike, del { text-decoration: line-through; }
code {
  font-family: 'Courier New', Consolas, monospace;
  background-color: #f5f5f5;
  padding: 2px 4px;
  border-radius: 3px;
}
pre {
  font-family: 'Courier New', Consolas, monospace;
  background-color: #f5f5f5;
  padding: 10px;
  margin: 0 0 8pt 0;
  white-space: pre-wrap;
  border-radius: 4px;
}
.hljs-addition, .hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag { color: #22863a; }
.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator,
.hljs-section, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-variable { color: #005cc5; }
.hljs-built_in, .hljs-symbol { color: #e36209; }
.hljs-bullet { color: #735c0f; }
.hljs-comment, .hljs-formula { color: #6a737d; }
.hljs-deletion { color: #b31d28; }
.hljs-doctag, .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type { color: #d73a49; }
.hljs-regexp, .hljs-string { color: #032f62; }
.hljs-title { color: #6f42c1; }
.hljs-emphasis { font-style: italic; }
.hljs-section, .hljs-strong { font-weight: bold; }
blockquote {
  border-left: 3px solid #ccc;
  margin: 0 0 8pt 0;
  padding-left: 10pt;
  color: #666;
  font-style: italic;
}
table {
  border-collapse: collapse;
  width: 100%;
  margin: 0 0 8pt 0;
}
th, td {
  border: 1px solid #ccc;
  padding: 6pt;
  text-align: left;
}
th {
  background-color: #f5f5f5;
  font-weight: bold;
}
a {
  color: #0066cc;
  text-decoration: underline;
}
img {
  max-width: 100%;
  height: auto;
}
hr {
  border: none;
  border-top: 1px solid #ccc;
  margin: 12pt 0;
}
sup { vertical-align: super; font-size: 8pt; }
sub { vertical-align: sub; font-size: 8pt; }
mark { background-color: #ffff00; }
`.trim();
export const DOCX_EXPORT_STYLES = `
body {
  font-family: 'Calibri', 'Arial', sans-serif;
  font-size: 11pt;
  line-height: 1.5;
  color: #000;
  margin: 0;
  padding: 20px;
}
h1 { font-size: 24pt; font-weight: bold; margin: 0 0 12pt 0; }
h2 { font-size: 18pt; font-weight: bold; margin: 0 0 10pt 0; }
h3 { font-size: 14pt; font-weight: bold; margin: 0 0 8pt 0; }
h4 { font-size: 12pt; font-weight: bold; margin: 0 0 6pt 0; }
h5 { font-size: 11pt; font-weight: bold; margin: 0 0 6pt 0; }
h6 { font-size: 10pt; font-weight: bold; margin: 0 0 6pt 0; }
p { margin: 0 0 8pt 0; }
ul, ol { margin: 0 0 8pt 0; padding-left: 20pt; }
li { margin: 0 0 4pt 0; }
strong, b { font-weight: bold; }
em, i { font-style: italic; }
u { text-decoration: underline; }
s, strike, del { text-decoration: line-through; }
code {
  font-family: 'Courier New', Consolas, monospace;
  background-color: #f5f5f5;




























































Format support

ContentBehavior
Paragraphs, headings, supported marks and propertiesDecode through the installed HTML mappings and export through the configured static renderer
Lists, links, bookmarks, rows, cells, and spansPreserve supported semantic structure; omit Word layout details with diagnostics
Tracked revisionsPreserve supported insert, delete, move, run-property, and paragraph-property changes
Main-body commentsPreserve rich bodies, ranges, author/date metadata, durable IDs, replies, and resolved state when present
Embedded images during importOmit the resource and return a resource-omitted diagnostic
Links and images during exportWrite absolute navigation-safe links and PNG, JPEG, GIF, or BMP images; report removed destinations and omitted images
Single-section headers and footersKeep safe closed source subgraphs during source-aware export; do not map them into the editor document
Multiple-section headers and footers, footnotes, and endnotesInspect and bound their package parts; omit them from edited source-aware output with diagnostics
Fields, text boxes, shapes, charts, SmartArt, and embedded objectsKeep reachable plain text when conversion exposes it; report unsupported or omitted content
Plate named roots and metadataOmit from ordinary Word output; preserve only when nativeState: "attach" writes a trusted corresponding part
Custom Plate, math, emoji, and media nodesUse their configured static serializers; unsupported output produces diagnostics

API reference

Entrypoints

SurfaceImport path
WordPastePluginplatejs/docx/paste
importDocx and import typesplatejs/docx/import
exportDocx and export typesplatejs/docx/export

importDocx

importDocx(
  source: ArrayBuffer | Blob,
  options: DocxImportOptions
): Promise<DocxImportResult>
importDocx(
  source: ArrayBuffer | Blob,
  options: DocxImportOptions
): Promise<DocxImportResult>

DocxImportOptions requires plugins and accepts schema, authoredTrust, a cooperative signal, lossPolicy, partial limits overrides, and retainSource. Import captures that detached target before its first await and never reads a live editor or mutable plugin store. Every limit must be a positive safe integer. Literal retainSource: true conditionally adds source: DocxSource | null to a successful result; omitted or literal false does not.

Default limitValue
maxInputBytes32 MiB
maxEntries1,024
maxEntryBytes32 MiB
maxExpandedBytes128 MiB
maxRelationships4,096
maxRevisions2,000
maxComments5,000
maxXmlDepth128
maxXmlNodes500,000

A successful result contains document, comments, and structured diagnostics. A retained success also contains source, which is null when exact export cannot admit the package. A failed result contains only diagnostics.

DocxSource

class DocxSource {
  private constructor();
  dispose(): void;
}
class DocxSource {
  private constructor();
  dispose(): void;
}

Only importDocx creates a DocxSource. The object is process-local and cannot be serialized or reattached. To restore source-aware export after application reload, keep the original file and import it again.

exportDocx

exportDocx(
  editor: Editor,
  options: DocxExportOptions
): Promise<DocxExportResult>
exportDocx(
  editor: Editor,
  options: DocxExportOptions
): Promise<DocxExportResult>
OptionTypeDescription
projection'accepted' | 'proposed' | 'review'Required visible document projection
commentsreadonly DocxComment[]Comment records in proposed-projection coordinates
allowRemoteImagesbooleanFetch remote HTTP(S) images to embed; defaults to false
componentReact.ComponentType<EditorStaticProps>Optional static editor wrapper
fontFamilystringDocument body font
lossPolicy'allow' | 'reject'Fail on dropped content, or return it as warnings; defaults to 'reject'
marginsMarginsPage margins in twentieths of a point
orientation'landscape' | 'portrait'Page orientation
pageSizePageSizePage dimensions in twentieths of a point
nativeState'attach'Attach Plate native state to a review export
signalAbortSignalCooperative cancellation signal
sourceDocxSource | nullRetained source correspondence for exact or safe export
stylesheetstringCSS applied before DOCX conversion
titlestringDocument metadata title

A successful result contains blob and structured diagnostics. A failed result contains only diagnostics.

See Authored Changes for the editor's accepted, proposed, and review projections.

plugins: [...DocxKit],
});
return {
comments: result.comments,
diagnostics: result.diagnostics,
};
}
(
!
result.ok)
return
result.diagnostics;
const url = URL.createObjectURL(result.blob);
const link = document.createElement("a");
link.href = url;
link.download = "document.docx";
document.body.append(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
return result.diagnostics;
}
padding: 2px 4px;
border-radius: 3px;
}
pre {
font-family: 'Courier New', Consolas, monospace;
background-color: #f5f5f5;
padding: 10px;
margin: 0 0 8pt 0;
white-space: pre-wrap;
border-radius: 4px;
}
.hljs-addition, .hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag { color: #22863a; }
.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator,
.hljs-section, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-variable { color: #005cc5; }
.hljs-built_in, .hljs-symbol { color: #e36209; }
.hljs-bullet { color: #735c0f; }
.hljs-comment, .hljs-formula { color: #6a737d; }
.hljs-deletion { color: #b31d28; }
.hljs-doctag, .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type { color: #d73a49; }
.hljs-regexp, .hljs-string { color: #032f62; }
.hljs-title { color: #6f42c1; }
.hljs-emphasis { font-style: italic; }
.hljs-section, .hljs-strong { font-weight: bold; }
blockquote {
border-left: 3px solid #ccc;
margin: 0 0 8pt 0;
padding-left: 10pt;
color: #666;
font-style: italic;
}
table {
border-collapse: collapse;
width: 100%;
margin: 0 0 8pt 0;
}
th, td {
border: 1px solid #ccc;
padding: 6pt;
text-align: left;
}
th {
background-color: #f5f5f5;
font-weight: bold;
}
a {
color: #0066cc;
text-decoration: underline;
}
img {
max-width: 100%;
height: auto;
}
hr {
border: none;
border-top: 1px solid #ccc;
margin: 12pt 0;
}
sup { vertical-align: super; font-size: 8pt; }
sub { vertical-align: sub; font-size: 8pt; }
mark { background-color: #ffff00; }
`.trim();