Getting started
@office-kit/docx is a library of plain functions. Every operation is a standalone export that takes a Docx value as its first argument, and nothing is hidden behind a class instance. That keeps the package tree-shakeable and makes it work the same way in Node and in the browser.
Install
pnpm add @office-kit/docx @office-kit/docx-preview @office-kit/docx is the authoring API. @office-kit/docx-preview is an optional companion that mounts a read-only preview of any Docx value into a DOM container. Both ship as ESM with bundled .d.ts types, and neither depends on a Node built-in.
Build a document from scratch
createDocx hands back a plain Docx object that the rest of the API treats as a value. Append to it, then call toUint8Array for the bytes of the file, or toBlob in the browser.
// Build a .docx from scratch. This file is imported as ?raw into the
// docs site so the snippet shown to readers is exactly what svelte-check
// type-checked — if an API rename breaks it, the docs build fails.
import {
addBulletList,
addTable,
appendHeading,
appendParagraph,
createDocx,
PAGE_SIZE_A4,
setPageSize,
toUint8Array,
} from "@office-kit/docx";
const doc = createDocx({ paragraphs: [] });
setPageSize(doc, PAGE_SIZE_A4);
appendHeading(doc, "Quarterly review", 1);
appendParagraph(doc, "Highlights from Q3.");
addBulletList(doc, ["Shipped preview", "Round-trip stable", "Docs site live"]);
addTable(doc, [
["Metric", "Q2", "Q3"],
["MRR", "$120k", "$148k"],
["Churn", "3.1%", "2.4%"],
]);
const bytes: Uint8Array = toUint8Array(doc);
void bytes;
Open a template and fill placeholders
An existing .docx can be opened with openDocx and edited in place. Every XML element the library does not model is kept as a pass-through node at its original position, so saving a template you did not change does not rewrite it into something else.
replaceTextEverywhere walks the body, headers, footers, footnotes, endnotes, and comments, not just the main document. A match such as {{name}} that Word split across several runs of one paragraph is still found; a match that spans two paragraphs is not.
// Open an existing .docx as a template, run cross-part placeholder
// substitution, write it back. The interesting bit: replaceTextEverywhere
// also walks headers, footers, footnotes, endnotes and comments — not just
// the body — so {{name}} in a header swaps out too.
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));
Render in the browser
@office-kit/docx-preview mounts a read-only preview of a Docx, or of raw bytes, into a DOM container. It wraps the open-source docx-preview renderer behind one function, previewToDOM.
Try it in the playground, or read the recipes for the embedding pattern.
Where to go next
- Recipes: common scenarios you can copy.
- API reference: every public export, grouped by area.
- Playground: drop in a .docx and see the preview.
- REPL: edit code and watch the document it builds.
- GitHub: source and issues.