Documentation

Live OOXML contract

DOCX fidelity and security

The Next.js export endpoint generates a native WordprocessingML package locally. This matrix is tied to the repository’s live package-level fidelity test; it is not a claim that arbitrary browser CSS has a one-to-one Word equivalent.

Tested mappings

AreaNative DOCX output
Document structureHeadings, paragraphs, block quotes, nested bullets and decimal lists, fixed-layout tables
Inline formattingBold, italic, underline, strike, code, font family and size, text/background color, subscript and superscript
Paragraph layoutAlignment, margins, line height, first-line indent, page-break-before and explicit page breaks
Page layoutLetter or A4, portrait or landscape, bounded margins, default headers and footers, current/total page fields
TablesExact DXA grid, repeating header rows, column span, row span, cell shading and vertical alignment
Review semanticsNative DOCX insertions, deletions, comments, footnotes and endnotes
EquationsLaTeX converted to native OMML — fractions, scripts, radicals, n-ary operators, delimiters, functions, limits, accents, bars and boxes; editable in Word's equation editor, not an image
Links and mediaHTTP/HTTPS/mailto links; PNG, JPEG, GIF and BMP data URIs; trusted server-resolved image map

Request contract

POST /api/export/docx
{
  "html": "<h1>Report</h1><p>Body</p>",
  "title": "Report",
  "fileName": "report.docx",
  "headerHtml": "<p>Private report</p>",
  "footerHtml": "<p>Page <span data-page-number="current"></span></p>",
  "page": { "size": "a4", "orientation": "landscape",
            "margin": { "top": 1080, "right": 900, "bottom": 1080, "left": 900 } },
  "imageMap": { "https://trusted.example/chart.png": "data:image/png;base64,..." }
}

Margins use twentieths of a point (twips) and are bounded from 360 to 2880. The request is capped at 1 MiB. An image map accepts at most eight HTTP(S) keys, data-image values only, and 768 KiB decoded in total.

Semantic HTML conventions

<ins data-author="Ada" data-date="2026-07-16T12:00:00Z">added</ins>
<del data-author="Ada">removed</del>
<span data-comment="Check the source" data-comment-author="Reviewer">claim</span>
<sup data-footnote="Source citation"></sup>
<span data-endnote="Appendix detail"></span>
<hr class="page-break">

Importing DOCX

Import runs entirely in the browser, with no library and no server round-trip. A .docx is a ZIP of XML: the ZIP central directory is read directly and DecompressionStream("deflate-raw") handles the inflate. Nothing is uploaded anywhere.

ImportedHow, and what a naive converter loses
Hyperlink URLsw:hyperlink carries only an r:id; the address lives in word/_rels/document.xml.rels. Resolved — without this, every link imports as plain text with the URL discarded.
Imagesw:drawinga:blipword/media/*, inlined as data URIs. Set documentImportImages: false to skip them on image-heavy files.
Numbered vs bulleted listsdocument.xml stores every list identically; only word/numbering.xml distinguishes them. Read, so numbered lists stay <ol> instead of collapsing to bullets.
List nestingw:ilvl produces real nesting, and the sublist is placed inside its parent <li> so the markup is valid.
Footnotes & endnotesWord keeps note bodies in separate word/footnotes.xml and word/endnotes.xml parts; document.xml holds only an id. All are read and rebuilt as live notes — insert one above an imported note and everything renumbers. The two parts share a schema but not an element name (w:footnote vs w:endnote), so a parser written for one silently returns nothing for the other. Word’s separator entries are skipped.
Text boxesText box content nests six levels below the run (w:pictv:shapev:textboxw:txbxContent), so a converter that reads only direct children drops every word of it. Recovered as a .rte-imported-textbox block in document order, paragraphs and tables intact. An mc:AlternateContent block carries the same box twice — a DrawingML version and a VML fallback — and only one is emitted.
Tracked changes & commentsw:ins and w:del import as <ins>/<del> with author and date — the same conventions the export emits, so review markup round-trips. Deleted text lives in w:delText, not w:t. Comment ranges become data-comment spans with the body pulled from word/comments.xml.
EquationsWord stores equations as an m:oMath tree in its own math namespace, interleaved between the runs — a converter that walks w:r elements never sees it, and the equation disappears while the sentence around it imports fine. Translated to LaTeX and emitted as the editor’s own rte-math-inline span, so it is editable in the math dialog and renders through whatever renderer the page loads. Fractions, sub/superscripts, radicals, n-ary operators (the operator is an attribute, not text), delimiters, functions, limits, accents, bars, matrices and aligned arrays are mapped; anything unrecognised keeps its literal text rather than vanishing.
SmartArtThe drawing in document.xml holds only relationship ids; every word of the diagram lives in word/diagrams/dataN.xml under a separate namespace, so a reader looking for a:blip finds nothing and drops the graphic entirely. The layout cannot be reproduced in HTML, but the content is recovered as a .rte-imported-smartart outline — an org chart that imports as a list is usable; one that imports as blank space is a hole. Word’s presentation and connector points (pres, parTrans, sibTrans) carry no content and are skipped.
WMF/EMF imagesWindows metafiles have no browser-renderable form, so they cannot be inlined. Rather than disappearing without trace, each leaves a named placeholder carrying data-source, so the reader knows a figure was there and which one to look up in the original.
Character formattingColour (w:color), highlight, superscript/subscript, and size — w:sz is in half-points, so 36 becomes 18pt rather than 36pt.
Structure & formattingHeadings from w:pStyle, tables, paragraph alignment, bold, italic, underline, strike-through, tabs and line breaks.

Markdown, HTML, plain text and Word’s HTML-flavoured .docare handled the same way. For anything beyond this — or to route conversion through your own service — supply config.documentImportResolver(file), which takes precedence for every format.

Known limits — and they are limits of shape, not of content. Nothing in a .docxis now dropped silently. What HTML cannot reproduce degrades visibly and says so: text boxes come back as content rather than floating frames (browser HTML has no equivalent of Word’s absolute page anchoring), SmartArt as an outline rather than a laid-out graphic, and WMF/EMF images as a named placeholder. Equations arrive as LaTeX, which needs KaTeX or MathJax on the page to render as maths rather than as their source. Those are import gaps, not export gaps — the table above covers what round-trips today. Equations are verified as a genuine loop rather than two independent features: fifteen formulas exported to OOXML and re-imported come back byte-identical, including the three visually identical Unicode angle brackets that different generators emit.

Security boundary

The converter never performs network fetches. A URL in an img element is ignored unless the calling server supplies an exact URL-to-data-URI entry in imageMap. This keeps DNS, redirects, private addresses, credentials and tenant authorization outside the converter’s trust boundary.

Intentional limits

  • Word is paginated and browser HTML is flow layout. Floats, CSS grid, filters, scripts, forms, animation and arbitrary styles are not translated.
  • Only default section headers and footers are exposed; first-page/even-page variants and multiple HTML-defined sections require an application-specific template.
  • The automated suite inspects the generated OOXML parts and relationships. Visual sign-off in Microsoft Word or LibreOffice remains a release step for branded templates.