DOCX
Patching
Modify existing .docx files by replacing placeholders, literal text, or metadata
Patch an existing .docx template in three ways: replace {{placeholder}} tokens, find-and-replace literal text, or override document metadata. Patch content can be inline runs, block-level elements (paragraphs, tables), images, and hyperlinks.
#patchDocument
Replaces placeholders in an existing .docx file:
import { patchDocument } from "@office-open/docx";
import { readFileSync, writeFileSync } from "node:fs";
const result = await patchDocument({
outputType: "nodebuffer",
data: readFileSync("template.docx"),
placeholders: {
name: {
type: "paragraph",
children: [{ text: "John Doe" }],
},
},
});
writeFileSync("output.docx", result);
#Patch types
Each patch is discriminated by a bare type string literal:
| Type | Description |
|---|---|
"paragraph" | Replace the placeholder with inline run-level content |
"document" | Replace the placeholder with block-level content |
#"paragraph"
Replaces the placeholder text inside a paragraph with new runs. The original run's formatting properties (font, size, color, bold, etc.) are preserved by default.
{
"placeholders": {
"title": {
"type": "paragraph",
"children": [
{
"text": "Hello ",
"bold": true
},
{
"text": "World"
}
]
}
}
}
#"document"
Replaces the placeholder with block-level elements (paragraphs, tables, etc.). The surrounding context is preserved.
placeholders: {
content: {
type: "document",
children: [
{ paragraph: { children: ["First paragraph"] } },
{ paragraph: { children: ["Second paragraph"] } },
{
table: {
rows: [
{
cells: [
{ children: [{ paragraph: { children: ["Cell"] } }] },
],
},
],
},
},
],
},
}
#Images
Replace a placeholder with an image:
placeholders: {
logo: {
type: "paragraph",
children: [
{
picture: {
type: "png",
data: readFileSync("logo.png"),
transformation: { width: "5.3cm", height: "2.6cm" },
},
},
],
},
}
#Hyperlinks
Include hyperlinks in patch content:
{
"placeholders": {
"website": {
"type": "paragraph",
"children": [
{
"text": "Visit "
},
{
"hyperlink": {
"children": [
{
"text": "our website"
}
],
"url": "https://example.com"
}
}
]
}
}
}
#Find and Replace
Replace literal text without any delimiters — the keys are matched verbatim. Useful for rebranding or updating fixed wording. Values use the same Patch shape (paragraph runs or block content):
const result = await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
findReplace: {
"Acme Corp": { type: "paragraph", children: [{ text: "Globex", bold: true }] },
Draft: { type: "paragraph", children: [{ text: "Final" }] },
},
});
#Core Properties
Override document metadata (docProps/core.xml). Values are merged over the existing core properties — supply only the fields you want to change:
const result = await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
coreProperties: { title: "Quarterly Report", creator: "Jane Doe" },
});
#Append Content
Append block-level content to the document body, inserted before the final section break (<w:sectPr>). It reuses the same SectionChild vocabulary as "document" patches, so paragraphs, tables, images, and hyperlinks are all supported:
const result = await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
append: [
{ paragraph: { children: ["Appended paragraph"] } },
{ paragraph: { children: [{ text: "Bold tail", bold: true }] } },
],
});
Appended content is serialized through the same compile-path stringifiers as generateDocument, and its images and hyperlinks are wired into the document's relationships automatically. Styles and numbering referenced by appended content must already exist in the template.
#Section Edits
Edit the section list itself — replace a whole section's content or append new sections. Each replacement entry names its 0-based section index (0 = from the document start):
const result = await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
sections: {
replace: [
{
index: 0,
section: {
children: [{ paragraph: { heading: "Heading1", children: ["Chapter 1 — replaced"] } }],
},
},
],
append: [
{
children: [{ paragraph: { children: ["Appendix"] } }],
properties: { pageSize: { width: 16838, height: 11906 } },
},
],
},
});
- Without
properties, a replaced section keeps its existing page setup; withproperties, the new page setup takes over. - Appended sections are inserted before the final body
sectPr, each closed by its own section break. A section withoutpropertiesinherits the final section's page setup. - Children serialize through the same pipeline as
append(paragraphs, tables, images, hyperlinks). headers/footersare not supported in patched-in sections — they would need new parts; usegenerateDocumentfor those.
#Comments
Inject comments into an existing document, merged with any existing word/comments.xml. Comment ids are continued from the highest existing id (or 0 when there are none). Two anchor kinds:
paragraphs— wrap a comment around the Nth body paragraph (0-based, by document order).placeholders— wrap a comment around the run containing a{{key}}token, applied before the placeholder is substituted.
Each comment reuses the CommentOptions vocabulary (see Comments and Revisions) but omits the id — the patch assigns continuation ids automatically.
const result = await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
comments: {
paragraphs: [
{
index: 0,
comments: [{ author: "Alice", children: ["Review the opening paragraph."] }],
},
],
placeholders: {
name: [{ author: "Bob", children: ["Verify this value."] }],
},
},
});
Existing comments are preserved — new entries are appended to word/comments.xml and re-serialized, and the comments relationship and content-type wiring are added when the part is new.
#Custom Delimiters
Default delimiters are {{ and }}. Change them with placeholderDelimiters:
await patchDocument({
outputType: "nodebuffer",
data: templateBuffer,
placeholders: { name: { type: "paragraph", children: [{ text: "John" }] } },
placeholderDelimiters: { start: "<<", end: ">>" },
});
#Options
| Option | Type | Default | Description |
|---|---|---|---|
outputType | string | — | Output format (see Export page) |
data | Buffer | Uint8Array | ... | — | Input .docx file data |
placeholders | Record<string, Patch> | — | Delimiter-wrapped placeholder name → patch content |
findReplace | Record<string, Patch> | — | Literal find string → patch content (no delimiters) |
coreProperties | Partial<CorePropertiesOptions> | — | Core metadata override, merged over existing values |
append | SectionChild[] | — | Block-level content appended before the final section break |
sections | { replace?, append? } | — | Section edits: replace is an array of { index, section } entries |
comments | { paragraphs?, placeholders? } | — | Inject comments anchored to paragraphs or placeholder runs (merged with existing) |
keepOriginalStyles | boolean | true | Preserve original run formatting properties |
placeholderDelimiters | { start: string, end: string} | { start: "{{", end: "}}" } | Custom placeholder delimiters |
#patchDetector
Scan a template to discover all placeholder keys before patching:
import { patchDetector } from "@office-open/docx";
const placeholders = await patchDetector({
data: readFileSync("template.docx"),
});
// ["name", "title", "content", ...]
#Tips
- Placeholders span across split runs in Word — the library handles this automatically.
placeholdersandfindReplaceshare one engine; combine both in a single call.- Every occurrence of a placeholder is replaced, not just the first — in the body, headers, and footers alike.
- Use
keepOriginalStyles: true(default) to inherit the template's run formatting when replacing text. - Images and hyperlinks in patch content are automatically added to the document's relationships.