floodtide/dom-docx
main
Branches
Tags
Go to file
Code
Open more actions menu
Folders and files
Name
Name
Last commit message
Last commit date
Latest commit
History
42 Commits
42 Commits
.github/
workflows
.github/
workflows
docs
docs
examples
examples
output
output
scripts
scripts
src
src
tools
tools
.gitignore
.gitignore
.nvmrc
.nvmrc
AGENTS.md
AGENTS.md
API.md
API.md
CHANGELOG.md
CHANGELOG.md
CONTRIBUTING.md
CONTRIBUTING.md
LICENSE
LICENSE
README.md
README.md
package-lock.json
package-lock.json
package.json
package.json
tsconfig.build.json
tsconfig.build.json
tsconfig.json
tsconfig.json
View all files
Repository files navigation
README
Contributing
MIT license
More
items
dom-docx
Convert semantic
HTML fragments
to native, editable
Word documents
(OOXML): paragraphs, runs, lists, tables, images. Not screenshots or layout hacks.
Live demo:
dom-docx.com
. Try the converter, browse showcases, read the learn guide.
Built with a visual regression loop: render HTML in Chromium, convert to docx, rasterize via LibreOffice, score layout + structural fidelity against a human-validated metric, iterate. Latest scores:
TEST-SCORES.md
· methodology:
SCORING.md
.
Install
npm install dom-docx
Requires
Node.js ≥ 20
. No browser or Playwright is needed for the default
inline
path.
When is Playwright needed?
Entry
styleSource: “inline”
styleSource: “computed”
rasterizeInPlace
Node
(
dom-docx
)
Pure JS, no browser
Playwright + Chromium
Playwright + Chromium
(same headless page)
Browser
(
dom-docx/browser
)
Pure JS, no live DOM
Live page
: native
getComputedStyle
Live page
: canvas/SVG → PNG
in the tab
On Node,
playwright
is an
optional peer dependency
.
npm install dom-docx
pulls only
docx
,
cheerio
and
fflate
, nothing heavy. It is loaded lazily when you pass
styleSource: “computed”
or
rasterizeInPlace
. To use those paths, install Playwright and Chromium yourself, once:
npm install playwright
npx playwright install chromium
Playwright is also used by the
dev test harness
(not required to use the library). Contributors:
npm run setup
after clone.
LibreOffice is
not
needed to convert. It is only used for the visual test harness.
CLI
Convert a file without writing any code:
npx dom-docx input.html -o output.docx
npx dom-docx input.html
#
writes input.docx next to it
cat fragment.html
|
npx dom-docx – -o –
#
stdin → binary stdout (pipelines)
npx dom-docx input.html -s computed
#
stylesheet/class HTML (needs playwright installed)
npm install -g dom-docx
#
optional: install globally, then run “dom-docx” without npx
Input is a
body HTML fragment
, same as the API.
–help
for all options.
Quick start
Browser
import
{
convertHtmlToDocx
}
from
“dom-docx/browser”
;
const
html
=
`
Quarterly Report
Revenue grew 12% year over year.
- North America
- EMEA
`
;
const
blob
=
await
convertHtmlToDocx
(
html
)
;
// e.g. trigger a download in the browser
const
a
=
document
.
createElement
(
“a”
)
;
a
.
href
=
URL
.
createObjectURL
(
blob
)
;
a
.
download
=
“output.docx”
;
a
.
click
(
)
;
No Playwright, no Node. This runs entirely in the user’s tab. See
Browser bundle
below.
Node
import
{
writeFile
}
from
“node:fs/promises”
;
import
{
convertHtmlToDocx
}
from
“dom-docx”
;
const
html
=
`
Quarterly Report
Revenue grew 12% year over year.
- North America
- EMEA
`
;
const
docx
=
await
convertHtmlToDocx
(
html
)
;
await
writeFile
(
“output.docx”
,
docx
)
;
Pass a
body fragment only
(no
/
/
required). Defaults: US Letter, 1″ margins, Arial 10.5pt body text.
v0.1.x capability
Supported (default
styleSource: “inline”
):
Headings, paragraphs, lists (
-
/
-
including
list-style-type
), tables, links, inline formatting
Block backgrounds, blockquotes,
, simple flex rows (≤4 items)
data:
images; remote images via your
imageResolver
Page size/orientation/margins, default font, metadata, header/footer HTML, page numbers, table of contents, lang/direction
Low-complexity inline SVG (bars + text)
CSS bar divs in table cells (background + height/width → native shaded bands)
Advanced (optional
styleSource: “computed”
):
Resolves
blocks and class/
#id
selectors via
getComputedStyle
Node:
requires
playwright
(optional peer dependency, installed separately) + Chromium. The library launches headless Chromium to render the fragment
Browser bundle:
uses the
live DOM
in the user’s tab. No Playwright, no extra install
SPA fragment export:
pass
root
(browser) or
rootSelector
(Node + live
page
) when converting
element.innerHTML
so computed-style paths match the fragment tree
Inline is the supported default
for npm installs; computed is for stylesheets/classes or when you already have a rendered page
Charts & complex SVG (optional
rasterizeInPlace
):
Rasterizes
and complex
(e.g. Highcharts) to PNG
before conversion
Recommended for charts:
rasterizeInPlace: { scale: 2 }
— supersamples at 2× density for sharper images in Word (default
scale: 1
; max
4
)
Browser:
requires
root
. Clones off-screen by default so the live page is not mutated
Node:
uses the same Playwright/Chromium context as computed styles; ephemeral spawn pages mutate in place by default
Simple inline SVG (rect + text bar charts) still converts natively without rasterization
Not supported in v0.1.x:
External stylesheets on the inline path (use computed or inline all styles)
Web fonts, CSS grid/float layout, forms,
polish,
-
, table
rowspan
Header/footer first/even page variants; guaranteed multi-page layout fidelity
Complex SVG (paths, gradients,
) unless
rasterizeInPlace
is used on a live rendered page
See
AGENTS.md
for HTML authoring tiers and
API.md
for full options.
API
convertHtmlToDocx(html, options?)
Returns
Promise
(Node) with a valid
.docx
file.
import
{
convertHtmlToDocx
,
type
ConvertOptions
}
from
"dom-docx"
;
const
docx
=
await
convertHtmlToDocx
(
html
,
{
pageSize
:
"a4"
,
orientation
:
"landscape"
,
margins
:
{
top
:
0.75
,
bottom
:
0.75
}
,
// inches; omitted sides default to 1
defaultFont
:
{
family
:
"Georgia"
,
sizePt
:
11
}
,
metadata
:
{
title
:
"Q3 Report"
,
creator
:
"Finance"
}
,
headerHtml
:
"
Confidential
"
,
footerHtml
:
"
© 2026 ACME
"
,
pageNumber
:
true
,
lang
:
"en-US"
,
direction
:
"ltr"
,
coverHtml
:
"
Quarterly Review
"
,
// page 1, before the TOC
tocHtml
:
"
"
,
// your TOC; links jump to id="intro"
}
)
;
Options
Option
Default
Description
styleSource
"inline"
"inline"
parses
style=""
only (pure JS, fast).
"computed"
uses
getComputedStyle
. On
Node
this requires Playwright + Chromium; in the
browser bundle
it reads from the live DOM (no Playwright).
browser
/
page
—
Node only.
Reuse an open Playwright browser or page (computed styles and/or
rasterizeInPlace
). Not used by
dom-docx/browser
.
rootSelector
—
Node only.
CSS selector for the export root when converting
element.innerHTML
from a live Playwright
page
. Must match the node whose HTML you pass.
rasterizeInPlace
—
Rasterize
/ chart
to PNG
before conversion.
Recommended:
{ scale: 2 }
for chart exports.
Node:
Playwright page;
browser:
requires
root
. See
API.md
.
imageResolver
—
Hook to fetch non-
data:
(library never fetches on its own).
pageSize
"letter"
"letter"
,
"a4"
or
{ width, height }
in inches.
orientation
"portrait"
"landscape"
swaps dimensions.
margins
1
inch each
Per-side overrides in inches.
defaultFont
Arial 10.5pt
{ family, sizePt }
for body text without explicit CSS.
metadata
—
title
,
subject
,
creator
,
keywords[]
,
description
→
docProps/core.xml
.
headerHtml
/
footerHtml
—
HTML fragments for page header/footer.
pageNumber
false
Appends centered
Page N
field to footer.
lang
/
direction
—
Spell-check locale;
"rtl"
for right-to-left.
coverHtml
—
HTML fragment rendered as a cover page — the first content, before the TOC, followed by an automatic page break. Inline styles +
data:
images (e.g. a logo). Header/footer/page number are suppressed on the cover page.
tocHtml
—
HTML fragment rendered as a table-of-contents "slot" — placed after the cover, before the body.
You
control the markup/styling (numbered, boxed, columns…); in-page links (
) jump to the matching
id
in the body. Add a trailing
to put it on its own page.
Images
Only
data:
URLs embed automatically. For
http(s):
or file paths, supply a resolver. You control fetch policy and security:
const
docx
=
await
convertHtmlToDocx
(
html
,
{
imageResolver
:
async
(
src
)
=>
{
const
res
=
await
fetch
(
src
)
;
// your allowlist / SSRF checks
if
(
!
res
.
ok
)
return
null
;
return
{
data
:
new
Uint8Array
(
await
res
.
arrayBuffer
(
)
)
,
type
:
"png"
}
;
}
,
}
)
;
Browser bundle
For client-side conversion (returns a
Blob
).
No Playwright.
The bundle runs entirely in the user's browser.
import
{
convertHtmlToDocx
}
from
"dom-docx/browser"
;
// Inline styles: pass HTML string directly
const
blob
=
await
convertHtmlToDocx
(
htmlFragment
,
{
styleSource
:
"inline"
}
)
;
// Computed styles: render the fragment in the live DOM first, then convert
document
.
body
.
innerHTML
=
htmlFragment
;
const
blob2
=
await
convertHtmlToDocx
(
htmlFragment
,
{
styleSource
:
"computed"
}
)
;
// SPA export: pass the live root so computed paths match element.innerHTML
const
root
=
document
.
querySelector
(
".page-body"
)
!
;
const
blob3
=
await
convertHtmlToDocx
(
root
.
innerHTML
,
{
styleSource
:
"computed"
,
root
,
}
)
;
// Charts (Highcharts, canvas): rasterize to PNG on a clone, then convert
const
blob4
=
await
convertHtmlToDocx
(
root
.
innerHTML
,
{
styleSource
:
"computed"
,
root
,
rasterizeInPlace
:
{
scale
:
2
}
,
// recommended for charts; default scale is 1
}
)
;
Browser-only options:
root
,
document
,
rasterizeInPlace
. Build:
npm run build:browser
→
dist/browser/dom-docx.browser.js
.
For advanced usage (
buildDocxBuffer
, custom
StyleResolver
, engine architecture), see
API.md
.
What converts well
Optimized for
Word-friendly semantic HTML
: headings, paragraphs, lists, data tables, inline formatting, shaded callouts, simple flex rows.
Excellent
Good
Avoid (or use
rasterizeInPlace
)
Headings, lists, simple tables
Shaded banners, flex (≤4 items)
Live chart libraries (Highcharts, canvas) unless rasterized
Inline
strong
/
em
/ links
Table row/cell backgrounds
Complex SVG paths/gradients unless rasterized
Explicit page breaks (
break-before: page
,
break-after: page
)
CSS grid, floats, absolute layout
Short span highlights
Blockquotes,
External stylesheets (inline path)
Simple SVG bars (rect + text)
data:
images
Forms, web fonts, CSS grid/float layout
Full authoring guide for agents:
AGENTS.md
.
How it was built
dom-docx maps a practical HTML subset to native OOXML through a three-stage pipeline:
Style resolution
: inline
style=""
(default) or browser computed styles
Visitor
: Cheerio walk →
docx
paragraphs, tables, numbering, hyperlinks
Pack + patch
: generate OOXML, patch list numbering and shaded-block alignment for LibreOffice/Word PDF export
Quality is driven by an autonomous loop rather than one-off visual checks:
30+ regression cases
(defined in
tools/generator.ts
, run via
npm run score:suite
): human-validated
layout fidelity
(ink-projection structure comparison, 85.6% concordance with blind human quality ratings), plus guards for bad contrast, missing list markers, wrong text and imbalanced shaded blocks; raw pixel match is recorded as a regression tripwire
Engine score
: 50% visual (layout-based) + 35% editability (native structure, not 1×1 layout tables) + 15% compile speed
OSS benchmark
: same harness scores
html-to-docx
and
@turbodocx/html-to-docx
for ongoing comparison (
BENCHMARK.md
)
The default
inline
path is pure JavaScript (
docx
+
cheerio
+
fflate
) with no browser required.
Playwright is Node-only
: for
styleSource: "computed"
on the server and for the dev harness. The
dom-docx/browser
bundle never uses Playwright; computed styles come from the live page's
getComputedStyle
.
Full scoring formulas, subscores, calibration and the agent iteration workflow:
SCORING.md
.
Development
For contributors and harness runs (not required to use the library):
git clone …
&&
npm install
&&
npm run setup
npm run build
#
dist/ for npm pack
npm run typecheck
npm run score:suite
#
full visual + XML regression suite (cases: tools/generator.ts)
npm run score:suite:priority
#
fast subset of the same cases
#
Run one case: SUITE_ONLY=rasterize-in-place-chart npm run score:suite
npm run score:benchmark
#
vs html-to-docx + TurboDocx
npm run guard:config
#
ConvertOptions OOXML checks
Prerequisites for the harness:
LibreOffice
(
soffice
) for PDF rasterization,
Playwright Chromium
(
npm run setup
).
Documentation
Doc
Contents
API.md
Full API reference, engine architecture, usage patterns
AGENTS.md
HTML authoring guide for AI agents
SCORING.md
Validation methodology and engine score
TEST-SCORES.md
Latest suite metrics and per-case scores
BENCHMARK.md
Comparison vs OSS html-to-docx libraries
examples/
Sample HTML, DOCX output and side-by-side previews
SHOWCASE.md
How to run and extend showcase examples
CONTRIBUTING.md
Library vs harness layout, dev setup
License
MIT
About
Convert semantic HTML fragments to native, editable Word documents (OOXML)
Resources
Readme
License
MIT license
Contributing
Contributing
Uh oh!
There was an error while loading.
Please reload this page
.
Activity
Custom properties
Stars
2
stars
Watchers
0
watching
Forks
0
forks
Report repository
Releases
5
tags
Packages
0
Uh oh!
There was an error while loading.
Please reload this page
.
Contributors
Uh oh!
There was an error while loading.
Please reload this page
.
Languages
TypeScript
96.4%
JavaScript
3.6%