Add, Delete, and Replace Images in Word with JavaScript in React
Working with images in Word documents is one of the most common requirements in day-to-day office development. Whether you are adding a company logo to a contract template, cleaning up leftover placeholder pictures in a document, or batch-replacing the stamps and icons in a report, manipulating images dynamically keeps document content in sync with your business data. Spire.Doc for JavaScript handles Word image processing entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts, documents, and image resources — no backend server required.
This article covers three core features:
- Add an image to a Word document
- Delete images from a Word document
- Replace images in a Word document
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add an Image to a Word Document
Adding an image involves three phases: first, load the font files, the target Word document, and the image to be inserted into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and load the file, get the target section and create a DocPicture object, call LoadImage to load the image and set its position, size, and text wrapping style, then use ChildObjects.Insert to insert the picture at the specified position in the specified paragraph; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const insertImage = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the input document into VFS
const inputFileName = "BlankTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Load the image file to be inserted into VFS
const inputImgFileName = "Word.png";
await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Load the document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section
let section = doc.Sections.get_Item(0);
// Add a heading paragraph
let paragraph = section.AddParagraph();
paragraph.AppendText("The sample demonstrates how to insert an image into a document.");
paragraph.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
// Add a new paragraph to hold the picture
paragraph = section.AddParagraph();
paragraph.AppendText("This is a picture.");
// Create a picture object and load the image file
let picture = new docModule.DocPicture(doc);
picture.LoadImage({ imgFile: inputImgFileName });
// Set the position of the picture
picture.HorizontalPosition = 50.0;
picture.VerticalPosition = 60.0;
// Set the size of the picture
picture.Width = 200;
picture.Height = 200;
// Set the text wrapping style of the picture
picture.TextWrappingStyle = docModule.TextWrappingStyle.Through;
// Insert the picture at the beginning of the paragraph (index 0)
paragraph.ChildObjects.Insert(0, picture);
// Save the document
const outputFileName = "Insert Image.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Insert an Image into a Word Document</h1>
<button onClick={insertImage}>
Generate
</button>
</div>
);
}
export default App;
The code above loads the picture through DocPicture, sets its absolute position to (50, 60) and its size to 200×200, applies the Through text wrapping style, and finally inserts it at the beginning of the second paragraph

Delete Images from a Word Document
Deleting images also involves three phases: first, load the font files and the Word document to be processed into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and load the file, walk the sections, the paragraphs, and the child objects inside each paragraph level by level, and use DocumentObjectType.Picture to determine whether an object is a picture — if it is, call ChildObjects.Remove to remove it from the paragraph; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const RemoveImage = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the input document into VFS
const inputFileName = "ImageTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/static/data/`);
// Load the document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
let removedCount = 0;
// Iterate over every section and paragraph and delete the pictures in each paragraph
for (let i = 0; i < doc.Sections.Count; i++) {
let sec = doc.Sections.get_Item(i);
for (let j = 0; j < sec.Paragraphs.Count; j++) {
let para = sec.Paragraphs.get_Item(j);
// Collect all the picture objects in the paragraph
let pictures = [];
for (let k = 0; k < para.ChildObjects.Count; k++) {
let docObj = para.ChildObjects.get_Item(k);
if (docObj.DocumentObjectType == docModule.DocumentObjectType.Picture) {
pictures.push(docObj);
}
}
// Remove the pictures from the paragraph one by one
for (let m = 0; m < pictures.length; m++) {
para.ChildObjects.Remove(pictures[m]);
removedCount++;
}
}
}
// Save the document
const outputFileName = "Delete Image.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Delete Images from a Word Document</h1>
<button onClick={RemoveImage}>
Generate
</button>
</div>
);
}
export default App;
The code above walks the document structure and removes every picture object, while the text content and formatting in the paragraphs stay unchanged. The picture on the left is the document before deletion, and the one on the right is the same document after all the images have been removed

Replace Images in a Word Document
Replacing an image can be seen as a combination of deleting and adding. It involves three phases: first, load the font files, the target Word document, and the new image file into the WASM virtual file system via FetchFileToVFS; then walk the sections and paragraphs, locate the picture objects and record their index within the paragraph, their original size, and their text wrapping style, call ChildObjects.Remove to remove the old picture, and then create a new DocPicture and insert it at the original index with the same parameters; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const ReplaceImage = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the input document into VFS
const inputFileName = "ImageTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/static/data/`);
// Load the new picture that will take the place of the old ones into VFS
const inputImgFileName = "NewLogo.png";
await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}/static/data/`);
// Load the document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Iterate over every section and paragraph and replace the pictures in each paragraph
for (let i = 0; i < doc.Sections.Count; i++) {
let sec = doc.Sections.get_Item(i);
for (let j = 0; j < sec.Paragraphs.Count; j++) {
let para = sec.Paragraphs.get_Item(j);
// Collect the picture objects in the paragraph along with their index in ChildObjects
let pictures = [];
for (let k = 0; k < para.ChildObjects.Count; k++) {
let docObj = para.ChildObjects.get_Item(k);
if (docObj.DocumentObjectType == docModule.DocumentObjectType.Picture) {
pictures.push({ index: k, picture: docObj });
}
}
// Iterate backwards so that removing an object does not shift the remaining indexes
for (let m = pictures.length - 1; m >= 0; m--) {
let index = pictures[m].index;
let picture = pictures[m].picture;
// Record the size and the text wrapping style of the original picture
let width = picture.Width;
let height = picture.Height;
let wrappingStyle = picture.TextWrappingStyle;
// Remove the original picture
para.ChildObjects.Remove(picture);
// Create a new picture that keeps the size and wrapping style of the original one
let newPicture = new docModule.DocPicture(doc);
newPicture.LoadImage({ imgFile: inputImgFileName });
newPicture.Width = width;
newPicture.Height = height;
newPicture.TextWrappingStyle = wrappingStyle;
// Insert the new picture at the original index
para.ChildObjects.Insert(index, newPicture);
}
}
}
// Save the document
const outputFileName = "Replace Image.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Replace Images in a Word Document</h1>
<button onClick={ReplaceImage}>
Generate
</button>
</div>
);
}
export default App;
The code above removes each original picture while inserting the new picture at the index the original one occupied, reusing its size and its wrapping style, so the layout of the document is exactly the same as before the replacement

FAQ
The inserted image does not show up, or the image file cannot be found
Cause: The image file has not been loaded into the WASM virtual file system, or the path passed to FetchFileToVFS does not match the actual location of the file, so LoadImage cannot read the image data from VFS.
Solution: Load the image into VFS with FetchFileToVFS before inserting it, and make sure the target file name matches the file name passed to LoadImage:
await window.spire.FetchFileToVFS(
'Word.png', '', `${process.env.PUBLIC_URL}static/data/`
);
let picture = new wasmModule.DocPicture(doc);
picture.LoadImage({ imgFile: 'Word.png' });
Images are left behind, or end up misplaced, after deletion or replacement
Cause: ChildObjects.Remove shifts the indexes of all subsequent child objects forward. If you read ChildObjects.Count while removing objects in the same loop, some of the pictures are skipped; and when replacing, inserting at the new index taken after the removal puts the picture at the wrong position.
Solution: Collect the picture objects and their original indexes in one pass first, then iterate backwards over that collection, and insert the new picture at the original index that was recorded:
// Collect the pictures and their indexes first
pictures.push({ index: k, picture: docObj });
// Then iterate backwards, removing each picture and inserting the new one back at its original index
for (let m = pictures.length - 1; m >= 0; m--) {
para.ChildObjects.Remove(pictures[m].picture);
para.ChildObjects.Insert(pictures[m].index, newPicture);
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Add, Delete, and Modify Shapes in Word with JavaScript in React
Working with shapes in Word documents is one of the most common requirements in day-to-day office development. Whether you are stamping a rounded-rectangle signature box onto a contract template, annotating approval steps with a group of flowchart shapes, cleaning up leftover decorative shapes in a document, or restyling the shapes in an old template to match a new brand palette, manipulating shapes dynamically keeps document content in sync with your business data. Spire.Doc for JavaScript handles Word shapes entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts, documents, and image resources — no backend server required.
This article covers three core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add Shapes to a Word Document
Adding shapes involves three phases: first, load the font files into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, add a section and a paragraph in turn, and call Paragraph.AppendShape to insert a shape of the specified type and size into the paragraph, anchoring it to the page with HorizontalOrigin/VerticalOrigin while setting its absolute coordinates through HorizontalPosition/VerticalPosition; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const appendShape = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Create a new document
const doc = new docModule.Document();
// Add a section
let sec = doc.AddSection();
// Add a paragraph to hold the shapes
let paragraph = sec.AddParagraph();
let x = 60, y = 40, lineCount = 0;
for (let i = 1; i < 20; i++) {
if (lineCount > 0 && lineCount % 8 == 0) {
// Start a new page once 8 rows are filled, and reset the starting coordinates
paragraph.AppendBreak(docModule.BreakType.PageBreak);
x = 60;
y = 40;
lineCount = 0;
}
// Add a shape and set its size
let shape = paragraph.AppendShape(50, 50, docModule.ShapeType.fromValue(i));
// Position the shape in absolute coordinates relative to the page
shape.HorizontalOrigin = docModule.HorizontalOrigin.Page;
shape.HorizontalPosition = x;
shape.VerticalOrigin = docModule.VerticalOrigin.Page;
shape.VerticalPosition = y + 50;
// Calculate the coordinates of the next shape
x = x + shape.Width + 50;
if (i > 0 && i % 5 == 0) {
y = y + shape.Height + 120;
lineCount++;
x = 60;
}
}
// Save the document
const outputFileName = "Add Shapes.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Shapes to a Word Document</h1>
<button onClick={appendShape}>
Generate
</button>
</div>
);
}
export default App;
The code above inserts 19 different preset shapes in a loop, each 50×50, retrieving the shape type one by one with ShapeType.fromValue(i) and laying them out in rows and columns from the page origin — 5 shapes per row, and an automatic page break via AppendBreak once 8 rows are filled

When you need to insert several related shapes at once — building a flowchart out of rectangles, parallelograms, and arrows, for example — you can instead call Paragraph.AppendShapeGroup to create a shape group first, then add text boxes, arrows, and other child shapes to it with ChildObjects.Add so they are laid out together. Coordinates inside a shape group are relative to the group itself, so you need to work out the scale factor with Width / 1000.0 and Height / 1000.0, then divide the target coordinates of each child shape by that factor before assigning them to HorizontalPosition/VerticalPosition. A text box is created with new wasmModule.TextBox(doc) and its outline specified through SetShapeType; its Format.LineColor sets the stroke color in exactly the same way as StrokeColor does for a regular shape.
Delete Shapes from a Word Document
Deleting shapes involves three phases: first, load the font files and the Word document to be processed into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and load the file, walk the sections and paragraphs of the document level by level, and use DocumentObjectType to determine whether a child object in a paragraph is a shape — regular shapes and text boxes in a document both exist as type Shape, shape groups are ShapeGroup, while a text box created in memory with new wasmModule.TextBox(doc) and not yet saved is reported separately as TextBox, so all three types must be checked; once identified, collect the objects and remove them from the paragraph one by one with ChildObjects.Remove; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const removeShape = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'ShapeTemplate.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}static/data/`);
// Load the document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
let removedCount = 0;
// Iterate over every section and paragraph and delete the shapes in each paragraph
for (let i = 0; i < doc.Sections.Count; i++) {
let sec = doc.Sections.get_Item(i);
for (let j = 0; j < sec.Paragraphs.Count; j++) {
let para = sec.Paragraphs.get_Item(j);
// Collect the shapes in the paragraph in one pass: regular shapes and text boxes are Shape, shape groups are ShapeGroup
let shapes = [];
for (let k = 0; k < para.ChildObjects.Count; k++) {
let docObj = para.ChildObjects.get_Item(k);
let objType = docObj.DocumentObjectType;
if (objType == docModule.DocumentObjectType.Shape
|| objType == docModule.DocumentObjectType.ShapeGroup
|| objType == docModule.DocumentObjectType.TextBox) {
shapes.push(docObj);
}
}
// Remove the shapes from the paragraph one by one
for (let m = 0; m < shapes.length; m++) {
para.ChildObjects.Remove(shapes[m]);
removedCount++;
}
}
}
// Save the document
const outputFileName = "Delete Shapes.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Delete Shapes from a Word Document</h1>
<button onClick={removeShape}>
Generate
</button>
</div>
);
}
export default App;
The code above walks the document structure and removes every shape on the page, while the text content and formatting in the paragraphs stay unchanged

Modify Shapes in a Word Document
Modifying shapes involves three phases: first, load the font files and the Word document to be processed into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and load the file, walk the sections and paragraphs to locate the shape objects within them, and for regular shapes and text boxes set FillColor and StrokeColor directly to change the colors and set Rotation, Width, and Height to adjust the rotation angle and size, while for a shape group you drill down into its ChildObjects and modify each child shape in turn; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const modifyShape = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'ShapeTemplate.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}static/data/`);
// Load the document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Iterate over every section and paragraph and modify the shapes in each paragraph
for (let i = 0; i < doc.Sections.Count; i++) {
let sec = doc.Sections.get_Item(i);
for (let j = 0; j < sec.Paragraphs.Count; j++) {
let para = sec.Paragraphs.get_Item(j);
for (let k = 0; k < para.ChildObjects.Count; k++) {
let docObj = para.ChildObjects.get_Item(k);
let objType = docObj.DocumentObjectType;
// Modify the fill color, outline color, rotation, and size of regular shapes and text boxes
if (objType == docModule.DocumentObjectType.Shape) {
docObj.FillColor = docModule.Color.get_Orange();
docObj.StrokeColor = docModule.Color.get_Red();
docObj.Rotation = 15;
docObj.Width = docObj.Width * 1.2;
docObj.Height = docObj.Height * 1.2;
}
// Modify a shape group: drill into the group and change the outline color of each child shape
if (objType == docModule.DocumentObjectType.ShapeGroup) {
for (let n = 0; n < docObj.ChildObjects.Count; n++) {
let child = docObj.ChildObjects.get_Item(n);
child.StrokeColor = docModule.Color.get_Purple();
}
}
}
}
}
// Save the document
const outputFileName = "Modify Shapes.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Modify Existing Shapes in a Word Document</h1>
<button onClick={modifyShape}>
Generate
</button>
</div>
);
}
export default App;
The code above keeps each shape's original position and text while restyling every shape in the document with an orange fill and a red outline, scaling it up to 120% and rotating it 15 degrees, and the child shapes inside shape groups get the new outline color as well

FAQ
Shapes end up misplaced or pushed off the page after being added
Cause: The meaning of the coordinates in HorizontalPosition/VerticalPosition depends on the frame of reference (the origin). If HorizontalOrigin/VerticalOrigin are not set explicitly, the coordinates default to the paragraph or the column, so they shift along with paragraph indentation and page margins, and the shape drifts away from the intended position.
Solution: Specify the frame of reference with HorizontalOrigin/VerticalOrigin first, and then set the coordinate values:
// Position the shape relative to the page
shape.HorizontalOrigin = wasmModule.HorizontalOrigin.Page;
shape.HorizontalPosition = x;
shape.VerticalOrigin = wasmModule.VerticalOrigin.Page;
shape.VerticalPosition = y + 50;
Some objects are missed when deleting shapes
Cause: In Spire.Doc a shape does not have just one DocumentObjectType: regular shapes and text boxes are uniformly Shape, shape groups are ShapeGroup, and a text box created in memory and not yet saved is TextBox. Checking only for Shape will miss shape groups and unsaved text boxes. On top of that, ChildObjects.Remove shifts the indexes of all subsequent child objects forward, so reading ChildObjects.Count while removing during the same loop will also skip some objects.
Solution: Collect all three types into an array in one pass first, then process the array one item at a time; when modifying the child shapes inside a shape group, drill down into its ChildObjects and iterate separately:
// Collect the shape objects of all three types first
if (objType == wasmModule.DocumentObjectType.Shape
|| objType == wasmModule.DocumentObjectType.ShapeGroup
|| objType == wasmModule.DocumentObjectType.TextBox) {
shapes.push(docObj);
}
// Then remove them together
for (let m = 0; m < shapes.length; m++) {
para.ChildObjects.Remove(shapes[m]);
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Add a Table of Contents to an Existing Word Document with JavaScript in React
A more common situation in real-world work is a document that is already written and has a complete chapter structure, but no table of contents was generated at the time. There is no need to rearrange the content — adding a TOC field on top of the existing heading styles is enough to produce a complete table of contents with page numbers and hyperlinks. Spire.Doc for JavaScript opens and edits Word documents directly in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) — no backend server required.
Compared with creating a new document, adding a table of contents to an existing document involves two extra key steps: loading the original document from VFS with LoadFromFile, and moving the table of contents paragraph to the very beginning of the document with Paragraphs.Insert, instead of appending it to the end by default.
This article covers two core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add a Default Table of Contents
Adding a default table of contents to an existing document has three phases: first, load the font file and the Word document to be processed into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and load the document with LoadFromFile, create a new paragraph and insert the TOC field with AppendTOC, and move it to the very beginning of the document with Paragraphs.Insert(0, tocPara); finally, call UpdateTableOfContents to fill in the entries and page numbers, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
The input document used in the example, AddTocToExisting.docx, is a technical report with three chapters and eleven multi-level headings but no table of contents yet.
function App() {
const AddTableOfContentsToExistingDocument = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Make sure the WASM module has fully loaded
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the font and the existing Word document into VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'AddTocToExisting.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Create a document instance and load the existing document
const doc = new docModule.Document();
doc.LoadFromFile({ fileName: inputFileName });
// Get the first section of the document
let section = doc.Sections.get_Item(0);
// Create a new paragraph and insert the TOC field, collecting Heading 1 through Heading 3 entries
let tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
// Move the table of contents paragraph to the very beginning of the document
section.Paragraphs.Insert(0, tocPara);
// Update the table of contents to fill in entries and page numbers
doc.UpdateTableOfContents();
// Define the output file name and save
const outputFileName = "Add a Default TOC to an Existing Document.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger the download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Click the button below to add a default table of contents to an existing document</h1>
<button onClick={AddTableOfContentsToExistingDocument}>
Generate
</button>
</div>
);
}
export default App;
After the existing document is loaded with LoadFromFile and a TOC field is inserted, the table of contents is placed at the very beginning, while the original chapter content and layout remain unchanged

Add a Custom Table of Contents
The table of contents generated by AppendTOC uses Word's default field switches. When you need to control its exact behavior, you can construct a TableOfContent object directly and specify the switch string instead. The difference from the previous feature lies in how it is inserted: you must manually add the table of contents object to a paragraph, supply the field separator and field end marks, and assign the object to document.TOC. The commonly used field switches and their meanings are as follows:
| Switch | Description |
|---|---|
\o "1-3" |
Collects entries by built-in heading styles; here it means including Heading 1 through Heading 3 |
\h |
Turns table of contents entries into hyperlinks that jump to the corresponding chapter when clicked |
\z |
Hides page numbers and tab leaders in Web Layout view |
\u |
Collects entries by the outline level of the paragraphs |
If you want the table of contents to occupy its own page and be separated from the body, add a page break to the same section after inserting the table of contents paragraph:
tocPara.AppendBreak(docModule.BreakType.PageBreak);
function App() {
const CustomizeTableOfContent = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Make sure the WASM module has fully loaded
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the font and the existing Word document into VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'AddTocToExisting.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Create a document instance and load the existing document
const doc = new docModule.Document();
doc.LoadFromFile({ fileName: inputFileName });
// Get the first section of the document
let section = doc.Sections.get_Item(0);
// Construct a table of contents object with custom field switches
let toc = new docModule.TableOfContent(doc, "{\\o \"1-3\" \\h \\z \\u}");
// Add the table of contents object to a paragraph
let tocPara = section.AddParagraph();
tocPara.Items.Add(toc);
// Supply the field separator and field end marks
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
tocPara.AppendText("TOC");
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);
// Bind this table of contents to the document
doc.TOC = toc;
// Move the table of contents paragraph to the very beginning of the document
section.Paragraphs.Insert(0, tocPara);
// Update the table of contents to fill in entries and page numbers
doc.UpdateTableOfContents();
// Define the output file name and save
const outputFileName = "Add a Custom TOC to an Existing Document.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger the download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Click the button below to add a custom table of contents to an existing document</h1>
<button onClick={CustomizeTableOfContent}>
Generate
</button>
</div>
);
}
export default App;
The table of contents generated with a TableOfContent object and custom field switches has its entry levels, hyperlinks, and page numbers all determined by the switch string

FAQ
The table of contents appears at the end of the document instead of the beginning
Cause: AddParagraph appends a new paragraph to the end of its section by default, so inserting the TOC field directly on it naturally places the table of contents at the end as well. The content of an existing document has already been laid out, so the insertion position must be specified explicitly.
Solution: Create the table of contents paragraph first, then move it to the very beginning of the document with Paragraphs.Insert:
let tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
section.Paragraphs.Insert(0, tocPara);
The table of contents is empty
Cause: A TOC field collects entries by heading style. If the chapter headings in the original document were only manually bolded and enlarged without built-in heading styles such as Heading1 to Heading3 applied, no entries will appear in the table of contents after updating.
Solution: First check whether the headings in the original document use built-in heading styles. If not, re-apply the style to those paragraphs after loading the document:
let heading = section.Paragraphs.get_Item(2);
heading.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
Page numbers in the table of contents are missing or incorrect
Cause: AppendTOC only inserts the TOC field itself; the field content must be updated explicitly. If UpdateTableOfContents is not called before saving, the generated table of contents contains only the field code, with no entries or page numbers.
Solution: Call the update method before SaveToFile:
doc.UpdateTableOfContents();
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Add a Table of Contents to a New Word Document with JavaScript in React
Creating a table of contents for a long document lets readers locate chapters quickly, and makes it easy to re-sync entries and page numbers after the document structure changes. Using a "Spire.Doc Developer Guide" as an example, this article shows how to build a Word document with multi-level headings from scratch and add a table of contents to it. Spire.Doc for JavaScript builds and edits Word documents directly in the browser via WebAssembly, managing font resources through a virtual file system (VFS) — no backend server required.
This article covers two core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add a Default Table of Contents
In Word, a table of contents is essentially a TOC field whose entries come from the paragraphs in the document that have a heading style applied. Creating a default table of contents has three phases: first, load the font file into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and build the content with AddSection and AddParagraph, calling ApplyStyle on the paragraphs that should appear in the table of contents to apply heading styles, and inserting the TOC field at the beginning of the document with AppendTOC; finally, call UpdateTableOfContents to fill in the entries and page numbers, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
The sample document contains three chapters and twelve multi-level headings in total, spanning Heading 1 through Heading 3, which makes it easy to observe how the table of contents collects multi-level headings.
function App() {
const AddTableOfContentsToNewDocument = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Make sure the WASM module has fully loaded
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the font into VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Create a document instance and add a section
const doc = new docModule.Document();
let section = doc.AddSection();
// Insert a TOC field at the beginning of the document, collecting Heading 1 through Heading 3 entries
let tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
// Add the document title
let characterFormat = new docModule.CharacterFormat(doc);
characterFormat.FontName ="Arial";
let title = section.AddParagraph();
let titleRun = title.AppendText("Spire.Doc Developer Guide");
titleRun.ApplyCharacterFormat(characterFormat);
titleRun.CharacterFormat.FontSize = 24;
title.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
// Chapter 1 Overview (Heading 1)
let p = section.AddParagraph();
p.AppendText("Chapter 1 Overview").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("Spire.Doc for JavaScript lets developers create, edit, and save Word documents directly in the browser, with no backend service involved at any point.").ApplyCharacterFormat(characterFormat);;
// 1.1 What Is Spire.Doc for JavaScript (Heading 2)
p = section.AddParagraph();
p.AppendText("1.1 What Is Spire.Doc for JavaScript").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It is a Word document processing library built on WebAssembly that manages fonts and document files through a virtual file system (VFS) and exposes an API shaped the same as the .NET version.").ApplyCharacterFormat(characterFormat);;
// 1.2 Use Cases (Heading 2)
p = section.AddParagraph();
p.AppendText("1.2 Use Cases").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It suits scenarios that require document processing on the client side, such as online contract signing, batch report generation, and resume template filling.").ApplyCharacterFormat(characterFormat);;
// Chapter 2 Core Capabilities (Heading 1)
p = section.AddParagraph();
p.AppendText("Chapter 2 Core Capabilities").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("Spire.Doc for JavaScript covers the entire document processing chain, from content construction and layout adjustment to format export, all of which can be completed in the browser.").ApplyCharacterFormat(characterFormat);;
// 2.1 Document Processing (Heading 2)
p = section.AddParagraph();
p.AppendText("2.1 Document Processing").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It supports creating and modifying common document elements such as paragraphs, styles, tables, images, headers, and footers, while preserving the original layout information.").ApplyCharacterFormat(characterFormat);;
// 2.1.1 Paragraphs and Styles (Heading 3)
p = section.AddParagraph();
p.AppendText("2.1.1 Paragraphs and Styles").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
section.AddParagraph().AppendText("Add a paragraph with AddParagraph and apply a built-in style with ApplyStyle to quickly build a clearly structured document skeleton.").ApplyCharacterFormat(characterFormat);;
// 2.1.2 Tables and Images (Heading 3)
p = section.AddParagraph();
p.AppendText("2.1.2 Tables and Images").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
section.AddParagraph().AppendText("Tables and images can be written directly into a specified paragraph or nested inside a textbox, meeting the layout needs of complex documents.").ApplyCharacterFormat(characterFormat);;
// 2.2 Format Conversion (Heading 2)
p = section.AddParagraph();
p.AppendText("2.2 Format Conversion").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("SaveToFile converts documents to PDF, HTML, Markdown, and other formats, with the whole conversion completed in the browser.").ApplyCharacterFormat(characterFormat);;
// 2.3 Batch Processing (Heading 2)
p = section.AddParagraph();
p.AppendText("2.3 Batch Processing").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("Combined with the runtime efficiency of WebAssembly, multiple documents can be loaded at once and processed in sequence, avoiding frequent file uploads and downloads.").ApplyCharacterFormat(characterFormat);;
// Chapter 3 Getting Started (Heading 1)
p = section.AddParagraph();
p.AppendText("Chapter 3 Getting Started").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("This chapter covers the preparation needed to integrate Spire.Doc for JavaScript into a React project and produce your first document.").ApplyCharacterFormat(characterFormat);;
// 3.1 Environment Setup (Heading 2)
p = section.AddParagraph();
p.AppendText("3.1 Environment Setup").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("Install Spire.Doc for JavaScript in your React project and place the font files and WASM resources in the public directory to get started.").ApplyCharacterFormat(characterFormat);;
// 3.2 The First Example (Heading 2)
p = section.AddParagraph();
p.AppendText("3.2 The First Example").ApplyCharacterFormat(characterFormat);;
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("After initializing the module, create a Document instance, add content, and save it; then read the resulting file from VFS to trigger a browser download.").ApplyCharacterFormat(characterFormat);;
// Update the table of contents to fill in entries and page numbers
doc.UpdateTableOfContents();
// Define the output file name and save
const outputFileName = "Create a Default TOC in Word Document.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger the download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Click the button below to create a default table of contents in a Word document</h1>
<button onClick={AddTableOfContentsToNewDocument}>
Generate
</button>
</div>
);
}
export default App;
After a TOC field is inserted with AppendTOC and updated, the beginning of the document holds a default table of contents that collects three heading levels, complete with page numbers and hyperlinks.

Add a Custom Table of Contents
The table of contents generated by AppendTOC uses Word's default field switches. When you need to control its exact behavior, you can construct a TableOfContent object directly and specify the switch string instead. The difference from the previous feature lies in how it is inserted: you must manually add the table of contents object to a paragraph, supply the field separator and field end marks, and assign the object to document.TOC. The commonly used field switches and their meanings are as follows:
| Switch | Description |
|---|---|
\o "1-3" |
Collects entries by built-in heading styles; here it means including Heading 1 through Heading 3 |
\h |
Turns table of contents entries into hyperlinks that jump to the corresponding chapter when clicked |
\z |
Hides page numbers and tab leaders in Web Layout view |
\u |
Collects entries by the outline level of the paragraphs |
For example, changing the switch string to \o "1-2" means only Heading 1 and Heading 2 entries are collected, and Heading 3 no longer appears in the table of contents; removing \h means the entries no longer support jumping.
function App() {
const CustomizeTableOfContent = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Make sure the WASM module has fully loaded
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the font into VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Create a document instance and add a section
const doc = new docModule.Document();
let section = doc.AddSection();
// Construct a table of contents object with custom field switches
let toc = new docModule.TableOfContent(doc, "{\\o \"1-2\" \\h \\z \\u}");
// Add the table of contents object to a paragraph
let tocPara = section.AddParagraph();
tocPara.Items.Add(toc);
// Supply the field separator and field end marks
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
tocPara.AppendText("TOC");
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);
// Bind this table of contents to the document
doc.TOC = toc;
// Add the document title
let characterFormat = new docModule.CharacterFormat(doc);
characterFormat.FontName ="Arial";
let title = section.AddParagraph();
let titleRun = title.AppendText("Spire.Doc Developer Guide");
titleRun.ApplyCharacterFormat(characterFormat);
titleRun.CharacterFormat.FontSize = 24;
title.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
// Chapter 1 Overview (Heading 1)
let p = section.AddParagraph();
p.AppendText("Chapter 1 Overview").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("Spire.Doc for JavaScript lets developers create, edit, and save Word documents directly in the browser, with no backend service involved at any point.").ApplyCharacterFormat(characterFormat);
// 1.1 What Is Spire.Doc for JavaScript (Heading 2)
p = section.AddParagraph();
p.AppendText("1.1 What Is Spire.Doc for JavaScript").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It is a Word document processing library built on WebAssembly that manages fonts and document files through a virtual file system (VFS) and exposes an API shaped the same as the .NET version.").ApplyCharacterFormat(characterFormat);
// 1.2 Use Cases (Heading 2)
p = section.AddParagraph();
p.AppendText("1.2 Use Cases").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It suits scenarios that require document processing on the client side, such as online contract signing, batch report generation, and resume template filling.").ApplyCharacterFormat(characterFormat);
// Chapter 2 Core Capabilities (Heading 1)
p = section.AddParagraph();
p.AppendText("Chapter 2 Core Capabilities").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("Spire.Doc for JavaScript covers the entire document processing chain, from content construction and layout adjustment to format export, all of which can be completed in the browser.").ApplyCharacterFormat(characterFormat);
// 2.1 Document Processing (Heading 2)
p = section.AddParagraph();
p.AppendText("2.1 Document Processing").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("It supports creating and modifying common document elements such as paragraphs, styles, tables, images, headers, and footers, while preserving the original layout information.").ApplyCharacterFormat(characterFormat);
// 2.1.1 Paragraphs and Styles (Heading 3)
p = section.AddParagraph();
p.AppendText("2.1.1 Paragraphs and Styles").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
section.AddParagraph().AppendText("Add a paragraph with AddParagraph and apply a built-in style with ApplyStyle to quickly build a clearly structured document skeleton.").ApplyCharacterFormat(characterFormat);
// 2.1.2 Tables and Images (Heading 3)
p = section.AddParagraph();
p.AppendText("2.1.2 Tables and Images").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
section.AddParagraph().AppendText("Tables and images can be written directly into a specified paragraph or nested inside a textbox, meeting the layout needs of complex documents.").ApplyCharacterFormat(characterFormat);
// 2.2 Format Conversion (Heading 2)
p = section.AddParagraph();
p.AppendText("2.2 Format Conversion").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("SaveToFile converts documents to PDF, HTML, Markdown, and other formats, with the whole conversion completed in the browser.").ApplyCharacterFormat(characterFormat);
// 2.3 Batch Processing (Heading 2)
p = section.AddParagraph();
p.AppendText("2.3 Batch Processing").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("Combined with the runtime efficiency of WebAssembly, multiple documents can be loaded at once and processed in sequence, avoiding frequent file uploads and downloads.").ApplyCharacterFormat(characterFormat);
// Chapter 3 Getting Started (Heading 1)
p = section.AddParagraph();
p.AppendText("Chapter 3 Getting Started").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
section.AddParagraph().AppendText("This chapter covers the preparation needed to integrate Spire.Doc for JavaScript into a React project and produce your first document.").ApplyCharacterFormat(characterFormat);
// 3.1 Environment Setup (Heading 2)
p = section.AddParagraph();
p.AppendText("3.1 Environment Setup").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("Install Spire.Doc for JavaScript in your React project and place the font files and WASM resources in the public directory to get started.").ApplyCharacterFormat(characterFormat);
// 3.2 The First Example (Heading 2)
p = section.AddParagraph();
p.AppendText("3.2 The First Example").ApplyCharacterFormat(characterFormat);
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
section.AddParagraph().AppendText("After initializing the module, create a Document instance, add content, and save it; then read the resulting file from VFS to trigger a browser download.").ApplyCharacterFormat(characterFormat);
// Update the table of contents to fill in entries and page numbers
doc.UpdateTableOfContents();
// Define the output file name and save
const outputFileName = "Create a Custom TOC in Word Document.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger the download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Click the button below to create a custom table of contents in a Word document</h1>
<button onClick={CustomizeTableOfContent}>
Generate
</button>
</div>
);
}
export default App;
The table of contents generated with a TableOfContent object and custom field switches has its entry levels, hyperlinks, and page numbers all determined by the switch string.

FAQ
The generated table of contents is empty
Cause: A TOC field collects entries by heading style. If the paragraphs do not have built-in heading styles such as Heading1 to Heading3 applied, no entries will appear in the table of contents after updating, even if the TOC field was inserted successfully.
Solution: Call ApplyStyle on the paragraphs that should appear in the table of contents to apply a heading style:
p.AppendText("Chapter 1 Overview");
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
The table of contents contains fewer heading levels than expected
Cause: The two parameters of AppendTOC correspond to the starting and ending heading levels collected by the table of contents. If you pass AppendTOC(1, 2), Heading 3 will not appear in the table of contents. With custom switches, \o "1-2" produces the same result.
Solution: Adjust the parameter range to the levels you need to collect — for example, to include Heading 1 through Heading 3:
tocPara.AppendTOC(1, 3);
Page numbers in the table of contents are missing or incorrect
Cause: AppendTOC only inserts the TOC field itself; the field content must be updated explicitly. If UpdateTableOfContents is not called before saving, the generated table of contents contains only the field code, with no entries or page numbers.
Solution: Call the update method before SaveToFile:
doc.UpdateTableOfContents();
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
Get a Free License
If you wish to remove the evaluation message from the resulting document, or to eliminate functional limitations, please contact our sales team to request a 30-day temporary license.
Merge Word Documents with JavaScript in React
Merging Word documents is a common requirement in web applications — combining multiple contract attachments into a single document, appending supplementary content at the end of a report, or merging multi-chapter documents for output. Spire.Doc for JavaScript handles document merging entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required.
This article covers two core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Merge by Section
Merging by section follows a three-stage process: first, load font files and both Word documents into the WASM virtual file system via FetchFileToVFS; then instantiate Document to load the target and source documents, iterate through all sections of the source document, and clone each section to the target document using Sections.Add(section.Clone()); finally, read the merged file from VFS, wrap it as a Blob, and trigger a browser download. This approach preserves each section's independent structure — every section starts on a new page in the merged document.
function App() {
const mergeBySection = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName1 = 'Template_Docx_1.docx';
await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}data/`);
const inputFileName2 = 'Template_Docx_2.docx';
await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}data/`);
// Load the target document
const TarDoc = new docModule.Document();
TarDoc.LoadFromFile(inputFileName1);
// Load the source document
const SouDoc = new docModule.Document();
SouDoc.LoadFromFile(inputFileName2);
// Clone all sections from the source document and append them to the target
for (let i = 0; i < SouDoc.Sections.Count; i++) {
let section = SouDoc.Sections.get_Item(i);
TarDoc.Sections.Add(section.Clone());
}
// Define the output file name
const outputFileName = 'MergeBySection_out.docx';
// Save the merged document
TarDoc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Read the merged file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
// Release resources
TarDoc.Dispose();
SouDoc.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Merge Word By Section</h1>
<button onClick={mergeBySection}>
Generate
</button>
</div>
);
}
export default App;
Each section from the source document appears as an independent page in the merged document after section-by-section merging

Merge on Same Page
Unlike section-by-section merging, same-page merging does not create new sections from the source document. Instead, it clones individual document elements — paragraphs, tables, images, and other content — from the source and appends them to the same section of the target document. The process also has three stages: first, load font files and both Word documents into the WASM virtual file system via FetchFileToVFS; then load the target and source documents, iterate through Body.ChildObjects under each section of the source, and append each element to the target document's first section using ChildObjects.Add(obj.Clone()); finally, read the merged file from VFS, wrap it as a Blob, and trigger a browser download. This approach keeps content flowing continuously on the same page without introducing section breaks.
function App() {
const mergeOnSamePage = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName1 = 'Template_Docx_1.docx';
await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}data/`);
const inputFileName2 = 'Template_Docx_2.docx';
await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}data/`);
// Load the target document
const destinationDocument = new docModule.Document();
destinationDocument.LoadFromFile(inputFileName1);
let count = destinationDocument.Sections.Count;
// Load the source document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName2);
// Iterate through all content elements in the source document
// and clone them into the first section of the target document
for (let i = 0; i < doc.Sections.Count; i++) {
let section = doc.Sections.get_Item(i);
for (let j = 0; j < section.Body.ChildObjects.Count; j++) {
let obj = section.Body.ChildObjects.get_Item(j);
destinationDocument.Sections.get_Item(count-1).Body.ChildObjects.Add(obj.Clone());
}
}
// Define the output file name
const outputFileName = 'MergeOnSamePage_out.docx';
// Save the merged document
destinationDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Read the merged file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
// Release resources
destinationDocument.Dispose();
doc.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Merge On Same Page</h1>
<button onClick={mergeOnSamePage}>
Generate
</button>
</div>
);
}
export default App;
Source content is appended continuously to the same section of the target document without page breaks after same-page merging

FAQ
Formatting issues after merging
Cause: Style definitions (fonts, sizes, paragraph styles) differ between the source and target documents, causing style conflicts after merging. When merging by section, each section retains its own style settings, but cross-section style references may be lost.
Solution: Preserve the source document's original formatting by setting KeepSameFormat before merging:
srcDoc.KeepSameFormat = true;
Merged document cannot be opened
Cause: The MIME type or file extension of the output file is incorrect, preventing the browser or Word from properly identifying the file format. Alternatively, resources may not have been released correctly after saving, leaving VFS file handles open.
Solution: Use the correct DOCX MIME type:
const blob = new Blob([data], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
Also ensure that Dispose() is called on each Document object after every merge operation to avoid WASM memory leaks.
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. If you would like to remove the evaluation message from the output document, contact sales to apply for a temporary license.
Adjust Word Table Layout with JavaScript in React
Tables are core elements for organizing and presenting data in Word documents, and proper table layout directly impacts readability and professionalism. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, enabling you to auto-fit tables directly using a virtual file system (VFS) to manage fonts and files — no backend server required.
This article covers three core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
1. AutoFit to Contents
Auto-fitting a table to its contents involves three steps: first, load the font files and the target document into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, load the file, retrieve the target table, and call AutoFit with the AutoFitToContents parameter; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const autoFitToContents = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the document file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TableSample.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first table in the first section
let section = doc.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
// Auto-fit column widths based on cell content
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);
// Define the output file name
const outputFileName = "AutoFitToContents_output.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>AutoFit to Contents</h1>
<button onClick={autoFitToContents}>
Generate
</button>
</div>
);
}
export default App;
After applying the AutoFitToContents mode, each column width shrinks to match the actual length of its cell content, resulting in a compact table with no extra whitespace.

2. AutoFit to Window
Auto-fitting a table to the window allows the table width to adapt to the page width, which is ideal for scenarios where the table should fill the full page width.
function App() {
const autoFitToWindow = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the document file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TableSample.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first table in the first section
let section = doc.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
// Auto-fit the table to the page width
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);
// Define the output file name
const outputFileName = "AutoFitToWindow_output.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>AutoFit to Window</h1>
<button onClick={autoFitToWindow}>
Generate
</button>
</div>
);
}
export default App;
After applying the AutoFitToWindow mode, the table width expands to match the page width, with columns distributed proportionally.

3. Fixed Column Widths
When a table already has carefully designed column widths that should not change as content is added or removed, you can use the fixed column widths mode to prevent Word from automatically resizing columns.
function App() {
const fixedColumnWidths = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the document file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TableSample.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first table in the first section
let section = doc.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
// Fix column widths to prevent auto-resizing
table.AutoFit(docModule.AutoFitBehaviorType.FixedColumnWidths);
// Define the output file name
const outputFileName = "FixedColumnWidths_output.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Fixed Column Widths</h1>
<button onClick={fixedColumnWidths}>
Generate
</button>
</div>
);
}
export default App;
With fixed column widths enabled, the column sizes remain unchanged regardless of changes to cell content, ensuring consistent layout.

FAQ
AutoFit method does not change the table layout
Cause: The parameter type passed to the AutoFit method is incorrect, or the table is locked and does not allow layout adjustments.
Solution: Ensure you use the correct AutoFitBehaviorType enum value:
// AutoFit to contents
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);
// AutoFit to window
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);
// Fixed column widths
table.AutoFit(docModule.AutoFitBehaviorType.FixedColumnWidths);
Index out of range when accessing a table
Cause: The document does not contain a section or table at the specified index. Indexing starts from 0, but the document may have no corresponding object.
Solution: Check the section and table counts before accessing them:
if (document.Sections.Count > 0 && document.Sections.get_Item(0).Tables.Count > 0) {
let section = document.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Split Word Documents with JavaScript in React
In document management systems, splitting Word documents is a common requirement — separating merged multi-chapter documents into independent files by section breaks, or dividing long documents into shorter ones by page breaks. Spire.Doc for JavaScript performs document splitting directly in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required.
This article covers two core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Split by Section Break
Section breaks in a Word document separate different chapters or page layouts. Each section can have its own headers, footers, page numbering, and page setup. Splitting by section breaks is the most common and stable approach, ideal for restoring merged multi-chapter documents back into independent files.
The core workflow consists of three stages: first, load the font files and the target Word file into the WASM virtual file system; then instantiate a Document, load the file, iterate through all sections, and clone each section into a new Document object via Section.Clone(); finally, package the split files into a ZIP archive and trigger a browser download.
function App() {
const splitBySectionBreak = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Template_Docx_4.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Create output directory
let outputDir = 'output/';
window.dotnetRuntime.Module.FS.mkdirTree(outputDir);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Iterate through each section and clone into independent documents
for (let i = 0; i < doc.Sections.Count; i++) {
const newWord = new docModule.Document();
newWord.Sections.Add(doc.Sections.get_Item(i).Clone());
newWord.SaveToFile({
fileName: outputDir + `Section-${i}.docx`,
fileFormat: docModule.FileFormat.Docx2013
});
newWord.Dispose();
}
doc.Dispose();
// Read output files from VFS and package into ZIP for download
const JSZip = require('jszip');
const zip = new JSZip();
let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
items = items.filter(item => item !== '.' && item !== '..');
for (const item of items) {
const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
zip.file(item, fileData);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'SplitBySectionBreak.zip';
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Split Word Document By Section Break</h1>
<button onClick={splitBySectionBreak}>Generate</button>
</div>
);
}
export default App;
Independent Word documents generated after splitting by section break

Split by Page Break
Page breaks are manual or automatic pagination markers inserted within a document. Splitting by page breaks is suitable for dividing long documents by page, saving each page's content as an independent document — commonly used for report pagination, contract clause splitting, and similar scenarios.
Unlike splitting by section breaks, page breaks reside at the child-object level within paragraphs. This requires traversing the document's sections, paragraphs, and paragraph child objects layer by layer to detect Break elements with the PageBreak type. When splitting, you also need to clone the original document's styles and themes using methods such as CloneDefaultStyleTo, CloneThemesTo, and CloneCompatibilityTo to ensure the split documents retain full formatting.
function App() {
const splitByPageBreak = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'SplitWordFileByPageBreak.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Create output directory
let outputDir = 'output/';
window.dotnetRuntime.Module.FS.mkdirTree(outputDir);
// Load the original document
const original = new docModule.Document();
original.LoadFromFile(inputFileName);
// Create a new document and clone styles and themes
let newWord = new docModule.Document();
let section = newWord.AddSection();
original.CloneDefaultStyleTo(newWord);
original.CloneThemesTo(newWord);
original.CloneCompatibilityTo(newWord);
let index = 0;
// Iterate through all sections
for (let i = 0; i < original.Sections.Count; i++) {
let sec = original.Sections.get_Item(i);
// Iterate through all child objects in the section (paragraphs, tables, etc.)
for (let j = 0; j < sec.Body.ChildObjects.Count; j++) {
let obj = sec.Body.ChildObjects.get_Item(j);
if (obj instanceof docModule.Paragraph) {
let para = obj;
sec.CloneSectionPropertiesTo(section);
section.Body.ChildObjects.Add(para.Clone());
// Detect page breaks within paragraph child objects
for (let k = 0; k < para.ChildObjects.Count; k++) {
let parobj = para.ChildObjects.get_Item(k);
if (parobj instanceof docModule.Break &&
parobj.BreakType === docModule.BreakType.PageBreak) {
let breakIndex = para.ChildObjects.IndexOf(parobj);
// Remove the page break from the paragraph
section.Body.LastParagraph.ChildObjects.RemoveAt(breakIndex);
// Save the current document
newWord.SaveToFile({
fileName: outputDir + `Page-${index}.docx`,
fileFormat: docModule.FileFormat.Docx2013
});
index++;
// Create a new document to continue
newWord = new docModule.Document();
section = newWord.AddSection();
original.CloneDefaultStyleTo(newWord);
original.CloneThemesTo(newWord);
original.CloneCompatibilityTo(newWord);
sec.CloneSectionPropertiesTo(section);
// Handle remaining content after the page break
section.Body.ChildObjects.Add(para.Clone());
if (section.Paragraphs.get_Item(0).ChildObjects.Count === 0) {
section.Body.ChildObjects.RemoveAt(0);
} else {
while (breakIndex >= 0) {
section.Paragraphs.get_Item(0).ChildObjects.RemoveAt(breakIndex);
breakIndex--;
}
}
}
}
}
if (obj instanceof docModule.Table) {
section.Body.ChildObjects.Add(obj.Clone());
}
}
}
// Save the last document
newWord.SaveToFile({
fileName: outputDir + `Page-${index}.docx`,
fileFormat: docModule.FileFormat.Docx2013
});
original.Dispose();
newWord.Dispose();
// Package into ZIP for download
const JSZip = require('jszip');
const zip = new JSZip();
let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
items = items.filter(item => item !== '.' && item !== '..');
for (const item of items) {
const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
zip.file(item, fileData);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'SplitByPageBreak.zip';
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Split Word Document By Page Break</h1>
<button onClick={splitByPageBreak}>Generate</button>
</div>
);
}
export default App;
Word documents generated after splitting by page break

FAQ
Split document formatting differs from the original
Cause: When splitting by page break, only the paragraph content is cloned without also cloning the original document's styles, themes, and section properties. This causes the split documents to lose formatting information such as fonts, colors, and page setup.
Solution: After creating a new document, clone the original document's styles and themes using the following methods:
original.CloneDefaultStyleTo(newWord);
original.CloneThemesTo(newWord);
original.CloneCompatibilityTo(newWord);
sec.CloneSectionPropertiesTo(section);
Page break cannot be detected
Cause: Page breaks reside within paragraph child objects and are identified via the BreakType enumeration. If the traversal hierarchy is incorrect, or the instanceof check is not used to determine the object type, the page break may not be properly recognized.
Solution: Ensure detection follows the order: Paragraph.ChildObjects → instanceof Break → BreakType == BreakType.PageBreak:
for (let k = 0; k < para.ChildObjects.Count; k++) {
let parobj = para.ChildObjects.get_Item(k);
if (parobj instanceof docModule.Break &&
parobj.BreakType === docModule.BreakType.PageBreak) {
// Page break found
}
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Operate Word Comments with JavaScript in React
Comments are an indispensable feature in Word document collaboration, widely used for review, proofreading, and team discussion scenarios. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage document files — no backend server or Microsoft Word installation required.
This article covers three core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add a Comment to Specific Text
In real-world document review scenarios, we often need to add comments to a specific piece of text. The approach is: first, use the FindString method to locate the target text; then insert CommentMarkStart and CommentMarkEnd markers before and after the text; finally, add the Comment object to the paragraph, associating the comment with the specified text.
function App() {
const AddCommentForSpecificText = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the target Word document into VFS
const inputFileName = "CommentTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the file
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Call the custom function to add a comment to the specified text
InsertComments(doc, "Development", docModule);
// Save the document
const outputFileName = "AddCommentForSpecificText.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
// Release resources
doc.Dispose();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
// Custom function: add a comment for the specified keyword
function InsertComments(doc, keystring, wasmModule) {
// Find the target string in the document
let find = doc.FindString(keystring, false, true);
// Create comment start and end markers
let commentMarkStart = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentStart);
let commentMarkEnd = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentEnd);
// Create a comment object, set content and author
let comment = new wasmModule.Comment(doc);
comment.Body.AddParagraph().Text = "Test comments";
comment.Format.Author = "Administrator";
// Get the found text range and its containing paragraph
let range = find.GetAsOneRange();
let para = range.OwnerParagraph;
// Get the index of the text range within the paragraph
let index = para.ChildObjects.IndexOf(range);
// Add the comment to the paragraph
para.ChildObjects.Add(comment);
// Insert comment start and end markers before and after the target text
para.ChildObjects.Insert(index, commentMarkStart);
para.ChildObjects.Insert(index + 2, commentMarkEnd);
}
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Comment for Specific Text</h1>
<button onClick={AddCommentForSpecificText}>
Generate
</button>
</div>
);
};
export default App;
With the code above, you can search for a specific keyword in the document, insert comment markers at its position, and accurately associate the comment with the target text.

Extract Comments from a Document
After a document has been reviewed by multiple people, it often contains numerous comments. Extracting these comments in bulk makes it easy to consolidate review feedback or perform further processing. Spire.Doc provides the Comments collection, which we can iterate through to read the text content of each comment and then export it as a text file.
function App() {
const ExtractComment = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the target Word document into VFS
const inputFileName = "CommentSample.docx";
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the file
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Iterate through all comments and extract text content
let stringB = [];
for (let i = 0; i < doc.Comments.Count; i++) {
let comment = doc.Comments.get_Item(i);
for (let j = 0; j < comment.Body.Paragraphs.Count; j++) {
let p = comment.Body.Paragraphs.get_Item(j);
stringB.push(p.Text + "\n");
}
}
// Save the extracted comment content as a text file
const outputFileName = 'ExtractComment.txt';
const blob = new Blob([stringB.toString()], { type: "text/plain;charset=utf-8" });
// Release resources
doc.Dispose();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Extract Comments from Document</h1>
<button onClick={ExtractComment}>
Generate
</button>
</div>
);
};
export default App;
The doc.Comments collection provides access to all comments in the document, and the text content of each comment is retrieved via comment.Body.Paragraphs. The extracted content can be saved as a plain text file for easy review or import into other systems.

Reply to and Modify Comments
In team collaboration scenarios, replying to existing comments and modifying their content are common needs. Spire.Doc supports adding replies to comments via the ReplyToComment method, as well as modifying comment content or deleting unwanted comments. The following example demonstrates how to get the first comment in a document and add a reply containing an image.
function App() {
const ReplyToComment = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the target document and image into VFS
const inputFileName = "Comment.docx";
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
const imageFile = "logo.png";
await window.spire.FetchFileToVFS(imageFile, '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the file
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first comment in the document
let comment1 = doc.Comments.get_Item(0);
// Create a reply comment, set author and content
let replyComment1 = new docModule.Comment(doc);
replyComment1.Format.Author = "E-iceblue";
replyComment1.Body.AddParagraph().AppendText("Spire.Doc is a professional Word library for operating Word documents.");
// Add the reply comment to the original comment
comment1.ReplyToComment(replyComment1);
// Load the image and insert it into the reply comment
let docPicture = new docModule.DocPicture(doc);
docPicture.LoadImage(imageFile);
replyComment1.Body.Paragraphs.get_Item(0).ChildObjects.Add(docPicture);
// Save the document
const outputFileName = "ReplyToComment.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
// Release resources
doc.Dispose();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Reply to Comment</h1>
<button onClick={ReplyToComment}>
Generate
</button>
</div>
);
};
export default App;
In addition to replying to comments, you can delete a specific comment using the Comments.RemoveAt(index) method, or modify the text content of a comment using Body.Paragraphs.get_Item(0).Replace(...):
// Modify the content of the first comment
doc.Comments.get_Item(0).Body.Paragraphs.get_Item(0).Replace({ given: "original text", replace: "modified text", caseSensitive: false, wholeWord: false });
// Delete the second comment
doc.Comments.RemoveAt(1);

FAQ
Comments not displaying correctly in Word
Cause: The CommentMarkStart and CommentMarkEnd markers are inserted at incorrect positions, or the Comment object is not properly added to the paragraph. The start marker must be placed before the commented text, the end marker after it, and the Comment object itself must also be added to the same paragraph.
Solution: Ensure the correct sequence — first get the index of the target text, then insert CommentMarkStart, add the Comment to the paragraph, and finally insert CommentMarkEnd after the text.
Empty output when extracting comments
Cause: The document may contain no comments, or the comment content is stored in a nested structure. Additionally, if the document is loaded from an incorrect path or the file is not successfully loaded into VFS, comments cannot be read.
Solution: Check doc.Comments.Count before extraction to confirm it is greater than 0. Also ensure the document file is properly loaded via FetchFileToVFS:
await window.spire.FetchFileToVFS(
'CommentSample.docx', '', `${process.env.PUBLIC_URL}/static/data/`
);
Original comment lost after replying
Cause: The ReplyToComment method should be called on the original comment. If the reply comment is mistakenly used as the caller, or the method is called multiple times causing reference confusion, the comment structure may become corrupted.
Solution: Always call ReplyToComment on the existing original comment object in the document, passing the newly created Comment object as the parameter:
// Correct: original comment calls ReplyToComment, new comment as parameter
existingComment.ReplyToComment(replyComment);
Get a Free License
If you want to remove the evaluation message from the result documents or eliminate functional limitations, please contact sales to obtain a 30-day temporary license.
Operate Word Variables with JavaScript in React
Word document variables (DocVariable) provide a lightweight field mechanism that lets you define placeholders in a document and dynamically populate or update their content through code. This approach is especially useful for template-based document generation, batch mail merges, and automated report output. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required.
This article covers three core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Add Document Variables
Adding document variables follows three main steps: first, load font files into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, insert a DocVariable field in a paragraph, and assign a value to the variable using Variables.Add; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
import React from 'react';
function App() {
const addVariables = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Create a document object
const doc = new docModule.Document();
// Add a section
const section = doc.AddSection();
// Add a paragraph
const paragraph = section.AddParagraph();
// Insert a DocVariable field into the paragraph
paragraph.AppendField("A1", docModule.FieldType.FieldDocVariable);
// Assign a value to the variable
doc.Variables.Add("A1", "12");
// Update fields to display variable values
doc.IsUpdateFields = true;
// Define the output file name
const outputFileName = "AddVariables_out.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Document Variables</h1>
<button onClick={addVariables}>
Generate
</button>
</div>
);
};
export default App;
After adding variables via the Variables.Add method, the DocVariable fields in the document are replaced with the corresponding variable values.

Retrieve Document Variables
For Word template documents that already contain variables, you can retrieve variable information by index or by variable name. Spire.Doc provides multiple retrieval methods: getting the variable name and value by index, or getting the value directly by variable name.
import React from 'react';
function App() {
const retrieveVariables = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Template_Docx_6.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get variable name and value by index
const nameByIndex = doc.Variables.GetNameByIndex(0);
const valueByIndex = doc.Variables.GetValueByIndex(0);
// Get value directly by variable name
const valueByName = doc.Variables.get_Item("A1");
// Iterate through all variables
let stringBuilder = [];
stringBuilder.push("This document has following variables:\n");
for (let i = 0; i < doc.Variables.Count; i++) {
let name = doc.Variables.GetNameByIndex(i);
let value = doc.Variables.GetValueByIndex(i);
stringBuilder.push("Name: " + name + ", " + "Value: " + value + "\n");
}
// Write the result to a text file
const outputFileName = "RetrieveVariables_out.txt";
window.dotnetRuntime.Module.FS.writeFile(outputFileName, stringBuilder.join(""));
// Read the file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Retrieve Document Variables</h1>
<button onClick={retrieveVariables}>
Generate
</button>
</div>
);
}
export default App;
The retrieval result is output as a text file, clearly listing all variable names and their corresponding values in the document.

Remove Document Variables
When a template document contains variables that are no longer needed, you can remove them by name using the Variables.Remove method. After removal, set the IsUpdateFields property to update the fields, ensuring the generated document is clean and free of redundant data.
function App() {
const removeVariables = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Template_Docx_6.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Remove variable by name
doc.Variables.Remove("A1");
let name = doc.Variables.GetNameByIndex(0);
doc.Variables.Remove(name);
doc.Variables.Remove(doc.Variables.GetNameByIndex(0));
doc.IsUpdateFields = true;
// Define the output file name
const outputFileName = "RemoveVariables_out.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Read the file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Remove Document Variables</h1>
<button onClick={removeVariables}>
Generate
</button>
</div>
);
}
export default App;
After removing variables, the target variables and their corresponding DocVariable fields are cleared from the generated document, resulting in cleaner content.

FAQ
Field codes still display instead of actual values after adding variables
Cause: After creating a DocVariable field, the document does not automatically update to show the variable value. If the IsUpdateFields property is not set, the field code text remains in the document.
Solution: Set IsUpdateFields to true before saving the document:
document.IsUpdateFields = true;
Getting a variable value by name returns empty
Cause: The variable name passed in does not match the actual variable name in the document (case or spelling mismatch), causing the lookup to fail.
Solution: First iterate through the document.Variables collection using GetNameByIndex to confirm the actual variable names in the document, then retrieve the value by the exact name:
for (let i = 0; i < document.Variables.Count; i++) {
let name = document.Variables.GetNameByIndex(i);
let value = document.Variables.GetValueByIndex(i);
console.log("Name: " + name + ", Value: " + value);
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
Operate Word Footnotes with JavaScript in React
Footnotes are a commonly used annotation tool in Word documents, allowing you to add supplementary explanations or citation references at the bottom of each page. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, enabling you to insert, format, and remove footnotes directly using a virtual file system (VFS) to manage fonts and files — no backend server required.
This article covers three core features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
1. Insert Footnotes
Inserting footnotes is a fundamental operation for adding annotations to a document — ideal for defining specific terms, citing sources, or providing supplementary explanations. Spire.Doc for JavaScript uses the AppendFootnote method to create footnotes and gives developers precise control over three aspects: where the footnote is inserted relative to the target text, the textual content displayed in the footnote body at the bottom of the page, and the visual style of the superscript footnote marker.
function App() {
const insertFootnote = async () => {
// Get the Spire.Doc WASM module
const wasmModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'SampleB_2.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Find the first matching string in the document
let selection = doc.FindString("Spire.Doc", false, true);
// Get the TextRange of the matched text
let textRange = selection.GetAsOneRange();
// Get the paragraph that contains the matched text
let paragraph = textRange.OwnerParagraph;
// Get the index position of the TextRange in the paragraph
let index = paragraph.ChildObjects.IndexOf(textRange);
// Append a footnote to the paragraph
let footnote = paragraph.AppendFootnote({ type: wasmModule.FootnoteType.Footnote });
// Insert the footnote after the matched text
paragraph.ChildObjects.Insert(index + 1, footnote);
// Add content to the footnote text body
textRange = footnote.TextBody.AddParagraph().AppendText("Welcome to evaluate Spire.Doc");
// Set the font format of the footnote content
textRange.CharacterFormat.FontName = "Arial Black";
textRange.CharacterFormat.FontSize = 10;
textRange.CharacterFormat.TextColor = wasmModule.Color.get_DarkGray();
// Set the format of the footnote marker (superscript number)
footnote.MarkerCharacterFormat.FontName = "Calibri";
footnote.MarkerCharacterFormat.FontSize = 12;
footnote.MarkerCharacterFormat.Bold = true;
footnote.MarkerCharacterFormat.TextColor = wasmModule.Color.get_DarkGreen();
// Define the output file name
const outputFileName = "InsertFootnote.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Insert Footnotes</h1>
<button onClick={insertFootnote}>
Generate
</button>
</div>
);
}
export default App;
After inserting a footnote via the AppendFootnote method, the annotation content appears at the bottom of the page, and a superscript footnote marker appears next to the corresponding text in the body.

2. Set Footnote Position and Number Format
By default, Word footnotes use Arabic numerals (1, 2, 3...) and restart at the beginning of each page's bottom area. However, academic papers, technical manuals, and publications with strict formatting guidelines often require different numbering schemes — such as letters or Roman numerals — and may need footnotes to be grouped at the end of each section rather than at the page bottom. Spire.Doc for JavaScript provides the FootnoteOptions object to give developers per-section control over the number format, restart rule, and display position, making it easy to comply with a wide range of typographic standards.
function App() {
const setFootnoteFormat = async () => {
// Get the Spire.Doc WASM module
const wasmModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the document file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Footnote.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section
let sec = doc.Sections.get_Item(0);
// Set footnote number format to uppercase letters
sec.FootnoteOptions.NumberFormat = wasmModule.FootnoteNumberFormat.UpperCaseLetter;
// Set footnote restart rule to restart per page
sec.FootnoteOptions.RestartRule = wasmModule.FootnoteRestartRule.RestartPage;
// Set footnote position to the end of the section
sec.FootnoteOptions.Position = wasmModule.FootnotePosition.PrintAsEndOfSection;
// Define the output file name
const outputFileName = "SetPositionAndNumberFormat.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Footnote Format</h1>
<button onClick={setFootnoteFormat}>
Generate
</button>
</div>
);
}
export default App;
After modifying the number format and position through the FootnoteOptions object, the footnotes in the document are reorganized according to the specified style. For example, setting NumberFormat to UpperCaseLetter (A, B, C...) and Position to PrintAsEndOfSection is well suited for appendices or chapter-based documents where footnotes should appear collectively at each section's end. Setting RestartRule to RestartPage ensures that footnote numbering resets on every page, preventing the counter from growing too large across long documents. Since these settings apply per section, different chapters or sections within the same document can each have their own footnote rules — a valuable feature for multi-chapter or collaborative authoring workflows.

3. Remove Footnotes
After multiple rounds of review or content updates, some footnotes may become obsolete or need to be removed. Spire.Doc for JavaScript handles this by iterating through all paragraphs in each section, checking every child object with instanceof to detect Footnote instances, and calling RemoveAt to delete them from the paragraph's child object collection. This paragraph-by-paragraph traversal approach reliably locates footnotes anywhere in the document without requiring prior knowledge of specific page numbers or index offsets.
function App() {
const removeFootnote = async () => {
// Get the Spire.Doc WASM module
const wasmModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the document file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Footnote.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section
let section = doc.Sections.get_Item(0);
// Traverse all paragraphs in the section to find and remove footnotes
for (let p = 0; p < section.Paragraphs.Count; p++) {
let para = section.Paragraphs.get_Item(p);
let index = -1;
// Check if each child object in the paragraph is a footnote
for (let i = 0, cnt = para.ChildObjects.Count; i < cnt; i++) {
let pBase = para.ChildObjects.get_Item(i);
if (pBase instanceof wasmModule.Footnote) {
index = i;
break;
}
}
// If a footnote is found, remove it from the paragraph
if (index > -1) {
para.ChildObjects.RemoveAt(index);
}
}
// Define the output file name
const outputFileName = "RemoveFootnote.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
// Read the generated file from VFS and trigger download
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Remove Footnotes</h1>
<button onClick={removeFootnote}>
Generate
</button>
</div>
);
}
export default App;
After removing footnotes, both the superscript markers in the body text and the annotation content at the page bottom are cleared, while the main text remains unaffected.

FAQ
Inserted footnote does not appear at the expected position
Cause: The footnote was appended to the paragraph but was not inserted into the correct child object order via the Insert method, causing the footnote marker to appear at the end of the paragraph instead of after the target text.
Solution: Use ChildObjects.IndexOf to get the index position of the target text, then use the Insert method to place the footnote after that position:
let index = paragraph.ChildObjects.IndexOf(textRange);
paragraph.ChildObjects.Insert(index + 1, footnote);
Footnote number format changes do not take effect
Cause: The footnote number format was set on the wrong section, or the document contains multiple sections but only the first section was modified.
Solution: Identify the section that contains the target footnotes and set the number format for each section that has footnotes:
for (let i = 0; i < document.Sections.Count; i++) {
let sec = document.Sections.get_Item(i);
sec.FootnoteOptions.NumberFormat = wasmModule.FootnoteNumberFormat.UpperCaseLetter;
}
Get a Free License
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.