Advanced functionality for modifying existing Word documents with new content while preserving the original document structure and formatting.
Patches an existing Word document template with new content based on placeholder tokens.
/**
* Patches an existing Word document with new content
* @param options - Patch configuration including template data and patches
* @returns Promise resolving to the patched document in specified format
*/
function patchDocument<T extends PatchDocumentOutputType = PatchDocumentOutputType>(
options: PatchDocumentOptions<T>
): Promise<OutputByType[T]>;
/**
* Patch document configuration options
*/
interface PatchDocumentOptions<T extends PatchDocumentOutputType> {
/** Template document data (Buffer, ArrayBuffer, etc.) */
readonly data: InputDataType;
/** Map of patch tokens to replacement content */
readonly patches: Record<string, IPatch>;
/** Output format type */
readonly outputType?: T;
/** Keep original document structure */
readonly keepOriginalStyles?: boolean;
}
/**
* Supported input data types for template documents
*/
type InputDataType = Buffer | ArrayBuffer | Uint8Array | Blob;
/**
* Supported output types for patched documents
*/
type PatchDocumentOutputType = "buffer" | "blob" | "uint8array" | "arraybuffer";Usage Example:
import { patchDocument, Paragraph, TextRun } from "docx";
import * as fs from "fs";
// Load template document
const templateBuffer = fs.readFileSync("template.docx");
// Define patches for placeholders in the template
const patches = {
"{{name}}": {
type: "paragraph",
children: [
new TextRun({
text: "John Doe",
bold: true,
}),
],
},
"{{company}}": {
type: "paragraph",
children: [
new TextRun("Acme Corporation"),
],
},
"{{date}}": {
type: "paragraph",
children: [
new TextRun(new Date().toLocaleDateString()),
],
},
};
// Patch the document
const patchedBuffer = await patchDocument({
data: templateBuffer,
patches: patches,
outputType: "buffer",
});
// Save the patched document
fs.writeFileSync("patched-document.docx", patchedBuffer);Detects patch placeholders in a template document to identify available tokens for replacement.
/**
* Detects patch placeholders in a template document
* @param options - Detection configuration including template data
* @returns Promise resolving to array of detected placeholder tokens
*/
function patchDetector(options: { readonly data: InputDataType }): Promise<readonly string[]>;Usage Example:
import { patchDetector } from "docx";
import * as fs from "fs";
// Load template document
const templateBuffer = fs.readFileSync("template.docx");
// Detect available placeholders
const placeholders = await patchDetector({
data: templateBuffer,
});
console.log("Found placeholders:", placeholders);
// Output: ["{{name}}", "{{company}}", "{{date}}", "{{items}}"]
// Use detected placeholders to create patches
const patches = {};
placeholders.forEach(placeholder => {
patches[placeholder] = {
type: "paragraph",
children: [new TextRun(`Replacement for ${placeholder}`)],
};
});Different types of patches for replacing content in templates.
/**
* Union type for all patch types
*/
type IPatch = ParagraphPatch | FilePatch;
/**
* Paragraph-level patch for replacing content within paragraphs
*/
interface ParagraphPatch {
readonly type: "paragraph";
readonly children: readonly ParagraphChild[];
}
/**
* File-level patch for replacing entire document sections
*/
interface FilePatch {
readonly type: "file";
readonly children: readonly FileChild[];
}
/**
* Content that can be children of paragraphs
*/
type ParagraphChild =
| TextRun
| ImageRun
| SymbolRun
| Bookmark
| PageBreak
| ColumnBreak
| FootnoteReferenceRun
| InternalHyperlink
| ExternalHyperlink
| Math
| SimpleField
| CheckBox;
/**
* Content that can be children of the document
*/
type FileChild =
| Paragraph
| Table
| TableOfContents;Complex patching scenarios with different content types.
Text and Formatting Patches:
import { patchDocument, TextRun, Paragraph } from "docx";
const patches = {
"{{title}}": {
type: "paragraph",
children: [
new TextRun({
text: "Annual Report 2024",
bold: true,
size: 32,
color: "2E74B5",
}),
],
},
"{{summary}}": {
type: "paragraph",
children: [
new TextRun("This report summarizes the key achievements of "),
new TextRun({
text: "fiscal year 2024",
bold: true,
}),
new TextRun(" including financial performance and strategic initiatives."),
],
},
};Table Patches:
import { Table, TableRow, TableCell, Paragraph } from "docx";
const tableData = [
["Product", "Sales", "Growth"],
["Widget A", "$125,000", "+15%"],
["Widget B", "$89,000", "+8%"],
["Widget C", "$156,000", "+22%"],
];
const patches = {
"{{sales_table}}": {
type: "file",
children: [
new Table({
rows: tableData.map((row, index) =>
new TableRow({
children: row.map(cell =>
new TableCell({
children: [
new Paragraph({
text: cell,
...(index === 0 && { bold: true }), // Header row
}),
],
})
),
})
),
}),
],
},
};Image Patches:
import { ImageRun, Paragraph } from "docx";
import * as fs from "fs";
const patches = {
"{{company_logo}}": {
type: "paragraph",
children: [
new ImageRun({
data: fs.readFileSync("logo.png"),
transformation: {
width: 200,
height: 100,
},
type: "png",
}),
],
},
};Best practices for creating patchable templates:
{{customer_name}} instead of {{x}}Template Example:
Dear {{customer_name}},
Thank you for your order dated {{order_date}}. Your order number is {{order_number}}.
{{order_items}}
Total: {{total_amount}}
Best regards,
{{sales_rep_name}}
{{company_name}}Patching operations can fail for various reasons. Handle errors appropriately:
import { patchDocument, patchDetector } from "docx";
try {
// First detect available placeholders
const placeholders = await patchDetector({ data: templateBuffer });
// Validate that required placeholders exist
const requiredPlaceholders = ["{{name}}", "{{date}}", "{{amount}}"];
const missingPlaceholders = requiredPlaceholders.filter(
placeholder => !placeholders.includes(placeholder)
);
if (missingPlaceholders.length > 0) {
throw new Error(`Missing placeholders: ${missingPlaceholders.join(", ")}`);
}
// Proceed with patching
const result = await patchDocument({
data: templateBuffer,
patches: patches,
outputType: "buffer",
});
console.log("Document patched successfully");
} catch (error) {
console.error("Patching failed:", error);
// Handle patching failure appropriately
}Patching can be combined with regular document creation for hybrid workflows:
import { Document, patchDocument, Paragraph, TextRun } from "docx";
// Create a base document
const baseDoc = new Document({
sections: [{
children: [
new Paragraph("{{header}}"),
new Paragraph("{{content}}"),
],
}],
});
// Convert to buffer first
const baseBuffer = await Packer.toBuffer(baseDoc);
// Then patch with dynamic content
const patchedBuffer = await patchDocument({
data: baseBuffer,
patches: {
"{{header}}": {
type: "paragraph",
children: [
new TextRun({
text: "Dynamic Header",
bold: true,
}),
],
},
"{{content}}": {
type: "paragraph",
children: [
new TextRun("Dynamic content generated at runtime"),
],
},
},
outputType: "buffer",
});Extended types and constants for comprehensive document patching.
/**
* Patch type constants
*/
const PatchType = {
/** Document-level patch (FileChild elements) */
DOCUMENT: "file",
/** Paragraph-level patch (ParagraphChild elements) */
PARAGRAPH: "paragraph"
} as const;
type PatchType = typeof PatchType[keyof typeof PatchType];
/**
* Input data type for patching operations
*/
type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream | JSZip;
/**
* Enhanced patch document options interface
*/
interface PatchDocumentOptions<T extends PatchDocumentOutputType = PatchDocumentOutputType> {
/** Output format type */
readonly outputType: T;
/** Input document data */
readonly data: InputDataType;
/** Patch definitions mapping tokens to content */
readonly patches: Readonly<Record<string, IPatch>>;
/** Whether to keep original document styles */
readonly keepOriginalStyles?: boolean;
/** Custom placeholder delimiter configuration */
readonly placeholderDelimiters?: Readonly<{
readonly start: string;
readonly end: string;
}>;
/** Search for occurrences over patched document recursively */
readonly recursive?: boolean;
}
/**
* Patch detector function for analyzing template placeholders
* @param options - Detection options with input data
* @returns Promise resolving to array of found placeholder tokens
*/
function patchDetector(options: { readonly data: InputDataType }): Promise<readonly string[]>;Usage Example:
import { patchDetector, PatchType, InputDataType } from "docx";
import * as fs from "fs";
// Analyze template for placeholders
const templateData = fs.readFileSync("template.docx");
const foundPlaceholders = await patchDetector({ data: templateData });
console.log("Found placeholders:", foundPlaceholders);
// Output: ["{{name}}", "{{date}}", "{{signature}}", "{{logo}}"]
// Create patches using discovered placeholders
const patches = foundPlaceholders.reduce((acc, placeholder) => {
if (placeholder === "{{name}") {
acc[placeholder] = {
type: PatchType.PARAGRAPH,
children: [new TextRun("John Doe")],
};
} else if (placeholder === "{{logo}}") {
acc[placeholder] = {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
data: fs.readFileSync("logo.png"),
transformation: { width: 100, height: 50 },
type: "png",
}),
],
};
}
return acc;
}, {} as Record<string, IPatch>);