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.
The app-local DocxKit installs WordPastePlugin. File import and export are standalone functions.
'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;import { createEditor } from "platejs/react";
import { DocxKit } from "@/components/editor/docx";
const editor = createEditor({
plugins: [...DocxKit],
});import { createEditor } from "platejs/react";
import { DocxKit } from "@/components/editor/docx";
const editor = createEditor({
WordPastePlugin inlines pasted CSS and normalizes Word HTML and RTF before the installed Plate format mappings decode the content.
import { WordPastePlugin } from "platejs/docx/paste";
import { createEditor } from "platejs/react";
const editor = createEditor({
plugins: [WordPastePlugin],
});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.
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.
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,
};
}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.
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.
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();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:
word/document.xml, styles, settings, numbering, fonts, and theme;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.
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.
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.
exportDocx captures the editor once before asynchronous rendering. Pass the visible projection explicitly.
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;
}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.
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.
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:
allowRemoteImages is off;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.
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:
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.
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.
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;
| Content | Behavior |
|---|---|
| Paragraphs, headings, supported marks and properties | Decode through the installed HTML mappings and export through the configured static renderer |
| Lists, links, bookmarks, rows, cells, and spans | Preserve supported semantic structure; omit Word layout details with diagnostics |
| Tracked revisions | Preserve supported insert, delete, move, run-property, and paragraph-property changes |
| Main-body comments | Preserve rich bodies, ranges, author/date metadata, durable IDs, replies, and resolved state when present |
| Embedded images during import | Omit the resource and return a resource-omitted diagnostic |
| Links and images during export | Write absolute navigation-safe links and PNG, JPEG, GIF, or BMP images; report removed destinations and omitted images |
| Single-section headers and footers | Keep safe closed source subgraphs during source-aware export; do not map them into the editor document |
| Multiple-section headers and footers, footnotes, and endnotes | Inspect and bound their package parts; omit them from edited source-aware output with diagnostics |
| Fields, text boxes, shapes, charts, SmartArt, and embedded objects | Keep reachable plain text when conversion exposes it; report unsupported or omitted content |
| Plate named roots and metadata | Omit from ordinary Word output; preserve only when nativeState: "attach" writes a trusted corresponding part |
| Custom Plate, math, emoji, and media nodes | Use their configured static serializers; unsupported output produces diagnostics |
| Surface | Import path |
|---|---|
WordPastePlugin | platejs/docx/paste |
importDocx and import types | platejs/docx/import |
exportDocx and export types | platejs/docx/export |
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 limit | Value |
|---|---|
maxInputBytes | 32 MiB |
maxEntries | 1,024 |
maxEntryBytes | 32 MiB |
maxExpandedBytes | 128 MiB |
maxRelationships | 4,096 |
maxRevisions | 2,000 |
maxComments | 5,000 |
maxXmlDepth | 128 |
maxXmlNodes | 500,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.
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(
editor: Editor,
options: DocxExportOptions
): Promise<DocxExportResult>exportDocx(
editor: Editor,
options: DocxExportOptions
): Promise<DocxExportResult>| Option | Type | Description |
|---|---|---|
projection | 'accepted' | 'proposed' | 'review' | Required visible document projection |
comments | readonly DocxComment[] | Comment records in proposed-projection coordinates |
allowRemoteImages | boolean | Fetch remote HTTP(S) images to embed; defaults to false |
component | React.ComponentType<EditorStaticProps> | Optional static editor wrapper |
fontFamily | string | Document body font |
lossPolicy | 'allow' | 'reject' | Fail on dropped content, or return it as warnings; defaults to 'reject' |
margins | Margins | Page margins in twentieths of a point |
orientation | 'landscape' | 'portrait' | Page orientation |
pageSize | PageSize | Page dimensions in twentieths of a point |
nativeState | 'attach' | Attach Plate native state to a review export |
signal | AbortSignal | Cooperative cancellation signal |
source | DocxSource | null | Retained source correspondence for exact or safe export |
stylesheet | string | CSS applied before DOCX conversion |
title | string | Document 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.