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:

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.

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: [
      {
        image: {
          data: readFileSync("logo.png"),
          width: "5.3cm",
          height: "2.6cm",
          type: "png",
        },
      },
    ],
  },
}

Include hyperlinks in patch content:

placeholders: {
  website: {
    type: "paragraph",
    children: [
      { text: "Visit " },
      {
        hyperlink: {
          children: [{ text: "our website" }],
          link: "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.

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 ) but omits the id — the patch assigns continuation ids automatically.

const result = await patchDocument({
  outputType: "nodebuffer",
  data: templateBuffer,
  comments: {
    paragraphs: {
      0: [{ 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

OptionTypeDefaultDescription
outputTypestringOutput 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
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
recursivebooleantrueReplace all occurrences (not just the first)

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.
  • placeholders and findReplace share one engine; combine both in a single call.
  • Use keepOriginalStyles: true (default) to inherit the template's run formatting when replacing text.
  • recursive: true (default) replaces all occurrences of each placeholder; set to false to replace only the first.
  • Images and hyperlinks in patch content are automatically added to the document's relationships.
Copyright © 2026