Patching
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.