CLI tool
floodtide/dom-docx avatar
floodtide/dom-docx

dom-docx: convert semantic HTML fragments to native, editable Word files

Convert semantic HTML fragments to native, editable Word documents (OOXML)

489 stars23 forksTypeScriptMIT

At a glance

What is it?
dom-docx turns a body HTML fragment into OOXML paragraphs, runs, lists, tables and images you can edit in Word. The default inline path is pure JavaScript, but anything class-based needs Chromium.
Who is it for?
Adopt dom-docx if you generate documents from a small, known set of HTML shapes (invoices, reports, contracts) and can keep that HTML inline-styled. Do not adopt it if you need to convert arbitrary third-party pages with external stylesheets, CSS grid layouts, web fonts or rowspan tables, since v1.x lists those as unsupported.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem dom-docx solves, and the developers it targets

Generating a .docx file is usually a choice between two bad options. You can write OOXML directly with a library such as docx, which means describing every paragraph, run and table cell in the library's own object model rather than in markup. Or you can hand the user a PDF and lose editability entirely. dom-docx takes a third position: you author a body HTML fragment, and the library maps it onto native OOXML elements. The README describes the output as "native, editable docx files", with paragraphs, runs, lists, tables, flex and images all supported.

The target reader is a developer who already has HTML. Server-rendered invoices, React dashboards, report templates built from a design system: these all exist as DOM trees before anyone asks for a Word download. dom-docx assumes that fragment is the input, not a full document. The README is explicit that you pass a body fragment only, with no <!DOCTYPE>, <html> or <body> wrapper required.

The package is TypeScript, MIT licensed, published as dom-docx version 1.0.1, and ships two entry points: dom-docx for Node and dom-docx/browser for the browser. That split matters more than the feature list, because the two entries have different dependency footprints.

How the conversion pipeline works, from fragment to OOXML

The library parses the fragment with cheerio, the HTML parser named in the package keywords and pulled in as a runtime dependency alongside docx and fflate. The parsed tree is walked and each supported element is translated into the corresponding OOXML construct. Style resolution is the part that decides how much machinery you need.

On the default inline path, styleSource is "inline". The README states this path is pure JavaScript with no browser, and that no Playwright is needed for it. Styles must arrive as inline attributes or as things the converter understands without a live DOM. On the computed path, styleSource is "computed", and the library resolves <style> blocks and class or #id selectors through getComputedStyle. In Node that means launching headless Chromium via Playwright; in the browser bundle it means reading the live DOM in the user's own tab, with no extra install.

There is a third stage for graphics. rasterizeInPlace takes <canvas> and complex SVG (the README names Highcharts) and rasterizes them to PNG <img> elements before conversion. The README recommends { scale: 2 } for charts, supersampling at twice the density, with a default of 1 and a maximum of 4. Simple inline SVG made of bars and text still converts natively, without rasterization.

One detail worth flagging for anyone converting a single-page app: when you pass element.innerHTML rather than a full document, computed-style matching can drift from the fragment tree. The library offers root (browser) and rootSelector (Node with a live page) for exactly that case.

Installing dom-docx and converting a first fragment

The package requires Node.js 20 or newer. The default install pulls only docx, cheerio and fflate, because Playwright is declared an optional peer dependency and loaded lazily.

bash
npm install dom-docx

The fastest way to see output without writing code is the CLI, which is exposed as the dom-docx binary. It accepts a file path and an output path, and writes the .docx next to the input when -o is omitted.

bash
npx dom-docx input.html -o output.docx

For programmatic use, import convertHtmlToDocx from the Node entry and write the returned buffer. The README's quick start uses a heading, a paragraph with a <strong> run, and an unordered list.

typescript
import { writeFile } from "node:fs/promises";
import { convertHtmlToDocx } from "dom-docx";

const html = `
<h1 style="color:#1a1a2e">Quarterly Report</h1>
<p>Revenue grew <strong>12%</strong> year over year.</p>
<ul>
  <li>North America</li>
  <li>EMEA</li>
</ul>
`;

const docx = await convertHtmlToDocx(html);
await writeFile("output.docx", docx);

Open output.docx and you should see the heading in the colour given inline, the bold 12% preserved as a run, and two bulleted list items. Defaults are US Letter, 1 inch margins and Arial 10.5pt body text, so an unstyled fragment still produces a readable document.

The browser entry uses the same function name and returns a Blob instead of a Buffer, which the README shows wired to a download link.

typescript
import { convertHtmlToDocx } from "dom-docx/browser";

const blob = await convertHtmlToDocx(html);
const a = document.createElement("a");

If your HTML relies on a stylesheet or classes rather than inline styles, the CLI takes -s computed, and you must install Playwright and Chromium first with npm install playwright followed by npx playwright install chromium. LibreOffice is not needed to convert; the README says it is used only by the visual test harness.

Where dom-docx stops: the unsupported list and the Playwright cliff

The README's own "Not supported in v1.x" list is the most useful page in the project. External stylesheets are unsupported on the inline path. Web fonts, CSS grid and float layout, forms, <pre> polish, <dl> and table rowspan are unsupported outright. Header and footer first and even page variants are unsupported, and multi-page layout fidelity is explicitly not guaranteed. Complex SVG with paths, gradients or <use> needs rasterizeInPlace on a live rendered page.

That list has a direct architectural consequence. The moment your markup depends on a stylesheet or a class selector, you leave the pure-JavaScript path and take on Chromium. For a serverless function or a container image, that is a large jump in size and cold-start cost, and the README does not document a lighter alternative for that case. If your HTML is already inline-styled, the dependency stays small; if it is not, plan for a browser.

The rasterization path carries its own caveat. In the browser it requires root, and it clones off-screen by default so the live page is not mutated. In Node it reuses the same Playwright context as computed styles, and ephemeral spawn pages mutate in place by default. Anyone converting a page the user is still looking at should read that sentence twice.

Finally, the fidelity claim itself is bounded. The project scores layout and structural fidelity through a visual regression loop, and points to docs/TEST-SCORES.md for the latest numbers and docs/SCORING.md for the methodology. Those are the project's own measurements against its own metric, not an independent comparison.

dom-docx compared with building OOXML directly

The obvious alternative is the docx package itself, which dom-docx depends on and wraps. With docx you construct a Document from Paragraph, TextRun and Table objects. That gives you exact control over styles, numbering and section properties, and it never needs a browser, because there is no HTML to resolve. The cost is that you now maintain two representations of the same content: the HTML your application renders and the object tree that produces the Word file.

dom-docx inverts that. Your HTML is the single source, and the library absorbs the mapping. For a report template that changes often, or for a React component that already renders the layout, that is a real reduction in duplicated work. For a document with precise typographic requirements, the abstraction is a liability: you are limited to what the converter recognises, and the unsupported list above tells you where that boundary sits.

A PDF pipeline is the other common alternative, and it is not really a substitute. PDFs preserve layout more faithfully but are not editable, which is the whole point of the OOXML target here. If your users need to open the file in Word and change a number, dom-docx is aimed at you; if they need a pixel-exact archive, it is not.

Maintenance, licence and the cost of upgrading

The repository is not archived, and its last push was on 2026-08-23. There are no releases retrieved for this project, so the published npm version, 1.0.1, is the reference point rather than a changelog entry. The repository does contain a CHANGELOG.md at the top level.

The licence is MIT, declared in package.json and shipped as LICENSE in the published files. That permits commercial use and modification, but the licence text itself is the authority, and this article is not legal advice.

Upgrade cost is shaped by the version boundary rather than by release cadence. The README frames the capability set as "v1.x", and the unsupported list reads like a roadmap of things that may land in a later major version. If external stylesheet support or rowspan tables arrive, the behaviour of existing input could shift, and the visual scoring harness suggests the project treats fidelity as something to be measured rather than assumed. Pin your version and re-run your own fixtures after an upgrade. The package ships examples/ in its published files, including invoice, balance-sheet and sales-contract directories, which give you ready-made inputs to test against.

Editorial conclusion

Adopt dom-docx if you generate documents from a small, known set of HTML shapes (invoices, reports, contracts) and can keep that HTML inline-styled. Do not adopt it if you need to convert arbitrary third-party pages with external stylesheets, CSS grid layouts, web fonts or rowspan tables, since v1.x lists those as unsupported. Before committing, run your own HTML through the CLI at npx dom-docx input.html -o output.docx and open the result in Word, then check whether your markup survives the inline path or forces the Playwright dependency.

Frequently asked questions

What is dom-docx used for?

It converts a body HTML fragment into a native, editable .docx file in OOXML format. The README lists headings, paragraphs, lists, tables, links, inline formatting, images, page setup, headers and footers as supported.

How can I convert a .docx file to HTML with dom-docx?

You cannot. dom-docx only goes one direction, from HTML to docx, and the README documents no reverse conversion.

What is the difference between HTML and the DOM in dom-docx?

dom-docx parses the HTML string you pass with cheerio, so the input is text rather than a live tree. When you need class or <style> resolution, styleSource: "computed" reads a real DOM through getComputedStyle, either in headless Chromium on Node or in the browser tab.

Does dom-docx need Playwright or a browser to convert?

Only for the optional paths. The README states the default inline path is pure JavaScript with no browser, while styleSource: "computed" and rasterizeInPlace require Playwright and Chromium on Node. In the browser bundle those paths use the live page instead.

What HTML is not supported by dom-docx v1.x?

The README lists external stylesheets on the inline path, web fonts, CSS grid and float layout, forms, <pre> polish, <dl>, table rowspan, header and footer first and even page variants, and guaranteed multi-page layout fidelity as unsupported.

Official sources

  1. floodtide/dom-docx on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/floodtide-dom-docx.svg)](https://hysenlabs.com/projects/floodtide-dom-docx)