Skip to content
Office Open

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:

ts
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:

TypeDescription
"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.

json
{
  "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.

ts
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:

ts
placeholders: {
  logo: {
    type: "paragraph",
    children: [
      {
        picture: {
          type: "png",
          data: readFileSync("logo.png"),
          transformation: { width: "5.3cm", height: "2.6cm" },
        },
      },
    ],
  },
}

Include hyperlinks in patch content:

json
{
  "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):

ts
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:

ts
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:

ts
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):

ts
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; with properties, the new page setup takes over.
  • Appended sections are inserted before the final body sectPr, each closed by its own section break. A section without properties inherits the final section's page setup.
  • Children serialize through the same pipeline as append (paragraphs, tables, images, hyperlinks).
  • headers/footers are not supported in patched-in sections — they would need new parts; use generateDocument for 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.

ts
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:

ts
await patchDocument({
  outputType: "nodebuffer",
  data: templateBuffer,
  placeholders: { name: { type: "paragraph", children: [{ text: "John" }] } },
  placeholderDelimiters: { start: "<<", end: ">>" },
});

#Options

OptionTypeDefaultDescription
outputTypestring—Output format (see Export page)
dataBuffer | Uint8Array | ...—Input .docx file data
placeholdersRecord<string, Patch>—Delimiter-wrapped placeholder name → patch content
findReplaceRecord<string, Patch>—Literal find string → patch content (no delimiters)
corePropertiesPartial<CorePropertiesOptions>—Core metadata override, merged over existing values
appendSectionChild[]—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)
keepOriginalStylesbooleantruePreserve 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:

ts
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.
  • placeholders and findReplace share 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.