Read, edit, and write Word documents in TypeScript
Build a .docx from nothing, or open one that already exists and change it. Paragraphs, lists, tables, images, headers, comments, and tracked changes are plain typed functions, and whatever the library does not model is written back untouched. It runs in Node and in the browser.
A document and the code that built it
export function buildHeroDocument(): Docx {
const doc = createDocx({ paragraphs: [] });
setPageSize(doc, PAGE_SIZE_A4);
setPageMargins(doc, MARGINS_NORMAL);
ensureHeadingStyles(doc);
appendHeading(doc, "Quarterly review", 1);
const intro = appendParagraph(doc, "Revenue grew 23% on last quarter. ");
appendTextRun(intro, "Churn hit a record low.", { bold: true });
appendHeading(doc, "Highlights", 2);
addBulletList(doc, [
"Shipped the self-serve onboarding flow",
"Cut median support response time to 2 hours",
"Opened the Osaka office",
]);
appendHeading(doc, "Numbers", 2);
const table = addTable(doc, [
["Metric", "Q2", "Q3"],
["Revenue", "$1.20M", "$1.48M"],
["Customers", "412", "497"],
["Churn", "3.1%", "2.4%"],
]);
setTableBorders(table, { color: "BFC5D2" });
const header = table.rows[0]!;
setTableRowAsHeader(header);
for (const cell of header.cells) setTableCellShading(cell, { fill: "E9EFFC" });
return doc;
}
Quarterly review
Revenue grew 23% on last quarter. Churn hit a record low.
Highlights
- Shipped the self-serve onboarding flow
- Cut median support response time to 2 hours
- Opened the Osaka office
Numbers
| Metric | Q2 | Q3 |
| Revenue | $1.20M | $1.48M |
| Customers | 412 | 497 |
| Churn | 3.1% | 2.4% |
That page is real output. The code shown with it built the document; this site saved it as .docx bytes, opened those bytes again with openDocx, and drew what it read as HTML. Download the file and open it in Word: the headings are styles, the list is a list, and the table is a table.
createDocx()Start from an empty document
createDocx() returns a document with its styles part and relationships already in place, so there is no template file to ship. Append headings, paragraphs, bullet and numbered lists, tables, inline images, headers, footers, footnotes, and a table of contents.
Write one in the REPLopenDocx(bytes)Edit a file you already have
Open a .docx, replace text across the body, headers, footers, footnotes, endnotes, and comments, swap an image by its alt text, accept or reject tracked insertions and deletions, and save. Whatever the library does not model goes back into the file as it came.
Browse the recipespreviewToDOM(doc, element)Show the document in the page
The companion @office-kit/docx-preview package renders a Docx, or raw bytes, into a DOM element. It wraps the open-source docx-preview renderer behind one function. It runs in the browser, so the file is never uploaded.
Try it in the playground
Design the template in Word. Fill it in from code.
The letterhead, the contract, the report with the right fonts: most documents that matter already exist as a .docx somebody cares about. Open it, replace what changes, and everything else stays as it was designed.
Word often splits a placeholder such as {{name}} across several runs. Matching works on the paragraph’s text rather than its XML, so it is still found, and replaceTextEverywhere reaches the headers, footers, notes, and comments as well as the body.
import { readFile, writeFile } from "node:fs/promises";
import { openDocx, replaceTextEverywhere, toUint8Array } from "@office-kit/docx";
const values: Record<string, string> = { name: "Ada Lovelace" };
const doc = openDocx(await readFile("letter-template.docx"));
const replaced = replaceTextEverywhere(
doc,
/\{\{(\w+)\}\}/g,
(m) => values[m.captures[0] ?? ""] ?? "",
);
console.log(`Filled ${replaced} placeholders.`);
await writeFile("letter.docx", toUint8Array(doc));
Round trips, checked by machines
A library that edits other people’s documents has one job above all: do not break them. This is what backs that up.
- Real Word files go in and come out the same.
- The test suite opens the .docx corpora that mammoth.js and python-docx test against, saves every file, reopens it, and compares the paragraphs and the text. CI runs it on Node 22 and Node 24 for every pull request.
- What the library does not understand, it does not touch.
- Elements it does not model are kept as pass-through nodes at their original position, and parts it does not read stay in the package. A content control or a custom XML part survives an edit.
- validate() looks for what makes Word offer a repair.
- Relationships that point at a missing part, comment, footnote and endnote references with no target, unpaired bookmarks, missing media. Twenty feature combinations are asserted clean in the test suite.
- You ship only the functions you import.
- 120 side-effect-free exports and no classes. createDocx, appendParagraph, and toUint8Array bundle to 42 KB minified; the whole API is 133 KB. CI fails if the small bundle passes 50 KB.
- One runtime dependency.
- fflate, for ZIP. The XML parser and serializer are written here, and nothing in the library imports a Node built-in.
How it differs from the docx package
The docx package is the established way to generate a Word file in JavaScript, and it can author things this library cannot yet. The difference is direction: that package builds new documents, while this library also reads and edits the ones you already have. Compared against docx 9.7.1.
| Capability | @office-kit/docx | docx (npm) |
|---|---|---|
| Open and edit an existing .docx | Yes. The file becomes a model: change any paragraph, run, or table | Placeholders only. patchDocument swaps {{tokens}} for new content |
| Read back what is in a file | Text, outline, statistics, images, fields, hyperlinks, bookmarks | No. patchDetector lists the placeholders and nothing else |
| What a placeholder can become | Text | Text, paragraphs, tables, or images |
| Tracked changes | Accepts or rejects the ones in a file; cannot author new ones | Authors insertions and deletions; cannot resolve existing ones |
| Merged cells, floating images, text boxes | Not yet | Yes |
| API shape | Tree-shakeable functions over plain data, ESM only | A tree of class instances; ESM, CommonJS, and script-tag bundles |
| Runtime dependencies | One: fflate | Six, including jszip and xml-js |
Pick the docx package if you only ever generate new documents and need merged cells, floating images, text boxes, or a CommonJS build. Pick this library if the document already exists and you need to read it, change it, or check it.
What you can build today
The library is pre-1.0 and says so. This is what works now, and what does not yet. The API reference lists every function.
- Text
- Paragraphs, headings, runs with bold, italic, underline, colour, highlight, size, and fonts. Alignment, indents, spacing, borders, shading.
- Lists and styles
- Bullet and numbered lists. Add, remove, and look up styles, or lift the whole style table from a designed template.
- Tables
- Rows, cell text, borders, cell shading, vertical alignment, row heights, repeating header rows. Unwrap a table back into paragraphs.
- Images
- Inline PNG, JPEG, GIF, BMP, TIFF, and SVG. List the images in a file and replace one by part name or alt text.
- Page setup
- Page size, margins, orientation, section breaks. Headers and footers for default, first, and even pages, with page numbers.
- Review
- Comments, footnotes, endnotes, bookmarks, external and internal hyperlinks. Accept or reject every tracked insertion and deletion.
- Fields
- Table of contents, merge fields, and PAGE, NUMPAGES, and DATE fields.
- Document
- Core and app properties, find and replace across every part, outline, statistics, plain-text extraction, validate().
Not yet
- Merged table cells and nested tables
- Floating images, text boxes, and charts (kept when already in a file)
- A typed API for content controls (kept when already in a file)
- ISO/IEC 29500 Strict files
- Rendering to PDF, which is out of scope for good
Readable by AI agents too
Documents are increasingly generated by agents, so the docs are published in a form a model can fetch directly. Every snippet in them is a file that is type-checked against the library when the site builds, so an agent is not handed an API that no longer exists.
/llms.txtAn index of the docs, for a model to pick from./llms-full.txtThe guides, the recipes, and the API listing in one Markdown file.
One kit, three file formats
Office Kit is a family of libraries built on the same rules: the ECMA-376 spec is the source of truth, files that go in must come out intact, and one ESM build has to run everywhere.
- .pptx @office-kit/pptx PowerPoint files. Slides, charts, tables, themes, notes, and animations. Open the pptx site
- .xlsx @office-kit/xlsx Excel files. Workbooks, formulas, styles, charts, and streaming for large sheets. Open the xlsx site
- .docx @office-kit/docx Word files. Paragraphs, lists, tables, page setup, and template editing. You are here