Comments and Revisions
Comments
Add a comment with the comment child, placed inline next to the text it covers:
{
"sections": [
{
"children": [
{
"paragraph": {
"children": [
"This text is ",
{
"comment": {
"author": "Author Name",
"initials": "AN",
"children": ["Please review this section."],
"wrap": [{ "text": "commented" }]
}
},
"."
]
}
}
]
}
]
}
import { generateDocument } from "@office-open/docx";
await generateDocument({
sections: [
{
children: [
{
paragraph: {
children: [
"This text is ",
{
comment: {
author: "Author Name",
initials: "AN",
date: new Date(),
children: ["Please review this section."],
wrap: [{ text: "commented" }],
},
},
".",
],
},
},
],
},
],
});
children is the comment reply (stored in the comments part — any paragraph content); wrap is the anchored document text the comment range covers (inline runs/text).
| Property | Type | Description |
|---|---|---|
author | string | Comment author (defaults to empty) |
initials | string | Author initials |
date | Date | string | Creation date (defaults to now) |
children | (string | ParagraphOptions)[] | Comment reply content |
wrap | (string | RunOptions)[] | Anchored text the range wraps |
Explicit markers (advanced)
For ranges that span paragraphs or tables, or to continue ids from an existing document, author the markers explicitly. The commentRangeStart, commentRangeEnd, and commentReference must share one id, which must also match a comments entry:
import { generateDocument } from "@office-open/docx";
await generateDocument({
comments: {
children: [
{
id: 0,
author: "Author Name",
date: new Date(),
children: ["Please review this section."],
},
],
},
sections: [
{
children: [
{
paragraph: {
children: [
{ commentRangeStart: { id: 0 } },
"This text has a comment attached.",
{ commentRangeEnd: { id: 0 } },
{ commentReference: 0 },
],
},
},
],
},
],
});
Track Revisions
Track insertions, deletions, and moves in documents:
{
"sections": [
{
"children": [
{
"paragraph": {
"children": [
"Original text, ",
{
"insertion": {
"id": 0,
"author": "Author",
"date": "2024-01-15T10:30:00Z",
"text": "inserted text"
}
},
", and ",
{
"deletion": {
"id": 1,
"author": "Author",
"date": "2024-01-15T10:30:00Z",
"text": "deleted text"
}
},
"."
]
}
}
]
}
]
}
import { generateDocument } from "@office-open/docx";
// Insertion and deletion together in one paragraph
await generateDocument({
sections: [
{
children: [
{
paragraph: {
children: [
"Original text, ",
{
insertion: {
id: 0,
author: "Author",
date: "2024-01-15T10:30:00Z",
text: "inserted text",
},
},
", and ",
{
deletion: {
id: 1,
author: "Author",
date: "2024-01-15T10:30:00Z",
text: "deleted text",
},
},
".",
],
},
},
],
},
],
});
Move Revisions
Track moved content with the moveFrom (source) and moveTo (destination) children. The moveFrom and moveTo of one logical move must share a name so Word links them:
{
"sections": [
{
"children": [
{
"paragraph": {
"children": [
"This paragraph had text moved ",
{
"moveFrom": {
"name": "my-move",
"author": "Author",
"date": "2024-01-15T10:30:00Z",
"wrap": [{ "text": "moved text" }]
}
},
"."
]
}
},
{
"paragraph": {
"children": [
"And it appeared here: ",
{
"moveTo": {
"name": "my-move",
"author": "Author",
"date": "2024-01-15T10:30:00Z",
"wrap": [{ "text": "moved text" }]
}
},
"."
]
}
}
]
}
]
}
import { generateDocument } from "@office-open/docx";
await generateDocument({
sections: [
{
children: [
{
paragraph: {
children: [
"This paragraph had text moved ",
{
moveFrom: {
name: "my-move",
author: "Author",
date: "2024-01-15T10:30:00Z",
wrap: [{ text: "moved text" }],
},
},
".",
],
},
},
{
paragraph: {
children: [
"And it appeared here: ",
{
moveTo: {
name: "my-move",
author: "Author",
date: "2024-01-15T10:30:00Z",
wrap: [{ text: "moved text" }],
},
},
".",
],
},
},
],
},
],
});
wrap is the moved content carried by the move run (inline runs/text). author and date apply to both the range start and the move run.
| Property | Type | Description |
|---|---|---|
name | string | Move name — share it across the from/to pair |
author | string | Author of the move |
date | string | ISO 8601 timestamp of the move |
wrap | (string | RunOptions)[] | Moved content carried by the move run |
colFirst | number | First column of a table-cell scope |
colLast | number | Last column of a table-cell scope |
displacedByCustomXml | "before" | "after" | Displacement vs. a sibling customXml |
Explicit markers (advanced)
Author the range markers and move runs separately when you need cross-paragraph ranges, explicit id continuation, or independent placement. moveFromRangeStart/moveFromRangeEnd mark the source, moveToRangeStart/moveToRangeEnd the destination; movedFrom/movedTo carry the moved text as tracked-change runs:
import { generateDocument } from "@office-open/docx";
await generateDocument({
sections: [
{
children: [
{
paragraph: {
children: [
{
moveFromRangeStart: {
id: 0,
name: "my-move",
author: "Author",
date: "2024-01-15T10:30:00Z",
},
},
{
movedFrom: {
id: 1,
author: "Author",
date: "2024-01-15T10:30:00Z",
children: ["moved text"],
},
},
{ moveFromRangeEnd: { id: 0 } },
],
},
},
{
paragraph: {
children: [
{
moveToRangeStart: {
id: 2,
name: "my-move",
author: "Author",
date: "2024-01-15T10:30:00Z",
},
},
{
movedTo: {
id: 3,
author: "Author",
date: "2024-01-15T10:30:00Z",
children: ["moved text"],
},
},
{ moveToRangeEnd: { id: 2 } },
],
},
},
],
},
],
});
Range starts accept id (required) plus name, author, date, and table-column scope (colFirst/colLast); range ends take just { id }.
Structured Document Tags (Content Controls)
Use SDT to create editable regions and form controls:
{
"sections": [
{
"children": [
{
"sdt": {
"properties": { "alias": "FullName", "tag": "full-name", "richText": true },
"children": [{ "paragraph": { "children": ["John Doe"] } }]
}
},
{
"sdt": {
"properties": {
"alias": "Color",
"tag": "combo-color",
"comboBox": {
"items": [
{ "displayText": "Red", "value": "red" },
{ "displayText": "Blue", "value": "blue" }
],
"lastValue": "Red"
}
},
"children": [{ "paragraph": { "children": ["Red"] } }]
}
}
]
}
]
}
import { generateDocument } from "@office-open/docx";
await generateDocument({
sections: [
{
children: [
// Block-level content control
{
sdt: {
properties: {
alias: "FullName",
tag: "full-name",
richText: true,
},
children: [{ paragraph: { children: ["John Doe"] } }],
},
},
// ComboBox content control
{
sdt: {
properties: {
alias: "Color",
tag: "combo-color",
comboBox: {
items: [
{ displayText: "Red", value: "red" },
{ displayText: "Blue", value: "blue" },
],
lastValue: "Red",
},
},
children: [{ paragraph: { children: ["Red"] } }],
},
},
],
},
],
});
SDTs support various control types for forms, templates, and document automation.
Document Protection
Restrict editing on the document using settings:
{
"features": {
"updateFields": true,
"documentProtection": {
"edit": "readOnly",
"password": "123"
}
},
"sections": [
{
"children": [{ "paragraph": { "children": ["Protected document content."] } }]
}
]
}
import { generateDocument } from "@office-open/docx";
const buffer = await generateDocument({
features: {
updateFields: true,
documentProtection: {
edit: "readOnly",
password: "123",
},
},
sections: [
{
children: [{ paragraph: { children: ["Protected document content."] } }],
},
],
});
Protection types: "readOnly", "comments", "trackedChanges", "forms".
When password is provided, a SHA-512 hash with random salt and 100,000 iterations is computed automatically. You can also provide pre-computed hash values directly.
| Option | Type | Description |
|---|---|---|
edit | string | Protection type |
password | string | Plaintext password (auto-hashed) |
algorithmName | string | Hash algorithm (e.g. "SHA-512") |
hashValue | string | Base64-encoded password hash |
saltValue | string | Base64-encoded salt |
spinCount | number | Number of hash iterations |
formatting | boolean | Restrict formatting |
hash | string | Legacy password hash (w:hash) |
salt | string | Legacy password salt (w:salt) |
cryptoAlgorithmClass | string | Cryptographic algorithm class |
cryptoAlgorithmSid | number | Cryptographic algorithm SID |
cryptoAlgorithmType | string | Cryptographic algorithm type |
cryptoProvider | string | Cryptographic provider |
cryptoProviderType | string | Cryptographic provider type |
cryptoProviderTypeExtension | number | Provider type extension |
cryptoProviderTypeExtensionSource | string | Provider type extension source |
algorithmExtensionId | number | Algorithm extension ID |
algorithmExtensionSource | string | Algorithm extension source |
cryptoSpinCount | number | Legacy cryptographic spin count |
Track Changes Options
When using insertion and deletion objects:
| Option | Type | Description |
|---|---|---|
id | number | Revision ID |
author | string | Author name for the revision |
date | string | ISO 8601 timestamp |
Both also accept all text run properties (text, bold, italic, color, etc.).