Knowledgebase (2416)
Children categories
In enterprise HR scenarios, batch contract generation is one of the most common document processing needs — monthly new employee onboarding, contract renewals, labor agreement changes often involve processing dozens or even hundreds of contracts at once. Each contract needs personalized information such as employee name, position, salary, and contract term.
Comparison with Traditional SDK API Processing
| Traditional Spire.Office for .NET API | Spire.Agent.Office | |
|---|---|---|
| Approach | Write code for traditional API processing: load template → get fields → read data → fill row by row → save, every step requires code control | Describe the goal in natural language, AI automatically orchestrates and completes all processing steps |
| Code Volume | Requires dozens of lines of code for data reading, field mapping, loop writing, and format control | Only configuration code + 1 natural language instruction |
| Field Mapping | Hard-code the mapping between merge fields and Excel columns; data source changes require code updates | AI automatically understands semantic correspondence between column names and template fields; data source changes require no code changes |
| Flexibility | Template field changes require code changes → compilation → redeployment | Just adjust the template or data source; existing instructions are reusable |
| Maintainability | Relies on development team to maintain code | Templates and data sources can be maintained directly by business users |
This article introduces how to use Spire.Agent.Office Word AI capabilities to automatically write Excel employee data into Word templates and generate contracts in PDF format in batches, using both mail merge and placeholder replacement approaches. You are also free to save as DOCX, DOC, HTML, OFD, Markdown, XPS, and other formats to meet different archiving needs.
For product installation and SpireToken configuration, please refer to Integrating Spire.Agent.Office in a .NET Project. The following examples assume Spire.Agent.Office is already installed and SpireToken is configured.
Mail Merge Approach
Mail merge is the standard solution for batch Word document generation and the most commonly used pattern in HR scenarios. The core idea is: a contract template Word document with merge fields and a data source, letting AI complete the data-to-template merge.
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Doc;
// Multiple document paths (data source files)
string[] attachmentPaths = new string[] { @"E:\data.xlsx" };
// Word template file path
string inputPath = @"E:\template-mailmerge.docx";
// Result document path (null here — will use the output folder path set below)
string savePath = null;
// Output directory
string OutDir = @"E:\output";
// SpireToken Key
string key = "**************************";
// Natural language instruction
string instruction =
"Execute mail merge: populate employee data from the attachment 'data.xlsx' into the merge fields of the contract template row by row; " +
"preserve the original document layout and styling after merging; " +
"generate one independent contract document per employee and save the output in PDF format";
// Call the Word document processing function
AIResult result = ExecuteDemoWord(instruction, inputPath, savePath, key, OutDir, attachmentPaths);
// Record processing log
WriteLog(result, "word", @"E:\log\");
// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string output, string[] attachmentPaths)
{
// Create AIOptions configuration object
AIOptions options = new AIOptions();
// Set working directory to output directory
options.WorkDir = output;
// Set SpireToken Key
options.SpireToken = key;
// Use Document object to process Word document
using (Document doc = new Document())
{
// Load Word template from file
if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
{
doc.LoadFromFile(inputPath);
}
// Create AI document processor
AIDocumentProcessor processor = doc.AI(options);
// Execute AI instruction
return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
}
}
Original Word template (with mail merge fields) and Excel data
Output generated via mail merge 
Each generated contract fully preserves the template's formatting, table styles, and font settings, with all merge fields replaced by the corresponding employee data. If 50 new employees are being onboarded, just one template + one Excel file + one instruction is all it takes to generate all contracts.
Placeholder Replacement Approach
The placeholder replacement approach does not require predefining mail merge fields in the template. Instead, it uses custom placeholder markers (such as {{Name}}, {{Salary}}) directly in the document, which the AI agent identifies and replaces.
// Multiple document paths (data source files)
string[] attachmentPaths = new string[] { @"E:\data.xlsx" };
// Contract template file path
string inputPath = @"E:\template.docx";
// Save path (null here — will use the output folder path set below)
string savePath = null;
// Output directory
string OutDir = @"E:\output";
// SpireToken Key
string key = "**************************";
// Natural language instruction
string instruction =
"Read employee data from 'data.xlsx' and replace the corresponding placeholders in the contract template row by row" +
"Highlight the replaced field content, preserve the original document layout, styling, and fonts after replacement," +
"Generate one independent contract document per employee and save the output in PDF format";
// Call the AI Word document processing method
AIResult result = ExecuteDemoWord1(instruction, inputPath, savePath, key, OutDir, attachmentPaths);
// Record processing log
WriteLog(result, "word", @"E:\log\");
// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string output, string[] attachmentPaths)
{
// Create AIOptions configuration object
AIOptions options = new AIOptions();
// Set working directory to output directory
options.WorkDir = output;
// Set SpireToken Key
options.SpireToken = key;
// Use Document object to process Word document
using (Document doc = new Document())
{
// Load Word template from file
if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
{
doc.LoadFromFile(inputPath);
}
// Create AI document processor
AIDocumentProcessor processor = doc.AI(options);
// Execute AI instruction
return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
}
}
Original Word template (with {{}} placeholders) and Excel data
Output generated via placeholder replacement 
Two Approaches Compared
| Mail Merge Approach | Placeholder Replacement Approach | |
|---|---|---|
| Template Creation | Requires inserting mail merge fields | Directly type {{}} placeholders |
| Learning Curve | Requires knowledge of Word mail merge functionality | Nearly zero learning cost |
| Flexibility | Fixed one-to-one field mapping | Supports dynamic calculation and formatting during replacement |
| Data Source | Requires structured data | Supports structured data, can also be defined in the instruction |
For creating Word templates with Spire.Agent.Office, please refer to the article "Creating Various Word Templates with Spire.Agent.Office".
Frequently Asked Questions
Generated document style changed
Cause: The AI model may modify or add content during processing.
Solution: Add a description like "preserve the original document layout, styling, and fonts" to the instruction.
Number of generated documents does not match the number of data rows after mail merge
Cause: Empty rows or merged cells in the data source Excel file, causing inaccurate row counting.
Solution: Ensure the first row of the data source contains column headers, with each subsequent row corresponding to one employee record and no empty rows in between. If the issue persists, add a sequence number column to the data source for validation.
Obtaining a SpireToken Key
- Contact sales@e-iceblue.com or visit https://www.e-iceblue.com/TemLicense.html to obtain a trial or commercial API key.
Configure it in code:
AIOptions options = new AIOptions();
options.SpireToken = key;
Add and Set Headers and Footers in Word with JavaScript in React
2026-07-07 03:40:23 Written by Amy ZhaoHeaders and footers are essential parts of any Word document — headers typically hold a company logo or document title, while footers display page numbers, copyright notices, and other supplementary information. Spire.Doc for JavaScript leverages WebAssembly to create and edit Word documents directly in the browser, managing fonts and file resources through a virtual file system (VFS) with no backend server required.
This article covers three core features:
- Add headers and footers (image, text, page number)
- Set a different header/footer for the first page
- Set different headers and footers for odd and even pages
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 Headers and Footers (Image, Text, Page Number)
In real-world development, the most common requirement is adding headers and footers to a document: inserting a company logo and document title in the header, and page numbers with copyright information in the footer. Spire.Doc provides the HeadersFooters.Header and HeadersFooters.Footer properties to access header and footer objects, then uses AppendPicture to insert images, AppendText to insert text, and AppendField to insert page number fields. The core workflow has three phases: first, load font files and the target Word document into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, load the file, retrieve the section, and call a custom function to populate the header and footer content; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function InsertHeaderAndFooter(section, inputImgFileName, inputImgFileName_1) {
let wasmModule = window.wasmModule.spiredoc;
let header = section.HeadersFooters.Header;
let footer = section.HeadersFooters.Footer;
// Insert an image and text in the header
let headerParagraph = header.AddParagraph();
let headerPicture = headerParagraph.AppendPicture({ imgFile: inputImgFileName });
// Header text
let text = headerParagraph.AppendText("Demo of Spire.Doc");
text.CharacterFormat.FontName = "Arial";
text.CharacterFormat.FontSize = 10;
text.CharacterFormat.Italic = true;
headerParagraph.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;
// Bottom border for the header
headerParagraph.Format.Borders.Bottom.BorderType = wasmModule.BorderStyle.Single;
headerParagraph.Format.Borders.Bottom.Space = 0.05;
// Header image layout - text wrapping
headerPicture.TextWrappingStyle = wasmModule.TextWrappingStyle.Behind;
// Header image layout - position
headerPicture.HorizontalOrigin = wasmModule.HorizontalOrigin.Page;
headerPicture.HorizontalAlignment = wasmModule.ShapeHorizontalAlignment.Left;
headerPicture.VerticalOrigin = wasmModule.VerticalOrigin.Page;
headerPicture.VerticalAlignment = wasmModule.ShapeVerticalAlignment.Top;
// Insert an image in the footer
let footerParagraph = footer.AddParagraph();
let footerPicture = footerParagraph.AppendPicture({ imgFile: inputImgFileName_1 });
// Footer image layout
footerPicture.TextWrappingStyle = wasmModule.TextWrappingStyle.Behind;
footerPicture.HorizontalOrigin = wasmModule.HorizontalOrigin.Page;
footerPicture.HorizontalAlignment = wasmModule.ShapeHorizontalAlignment.Left;
footerPicture.VerticalOrigin = wasmModule.VerticalOrigin.Page;
footerPicture.VerticalAlignment = wasmModule.ShapeVerticalAlignment.Bottom;
// Insert page number
footerParagraph.AppendField("page number", wasmModule.FieldType.FieldPage);
footerParagraph.AppendText(" of ");
footerParagraph.AppendField("number of pages", wasmModule.FieldType.FieldNumPages);
footerParagraph.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;
// Top border for the footer
footerParagraph.Format.Borders.Top.BorderType = wasmModule.BorderStyle.Single;
footerParagraph.Format.Borders.Top.Space = 0.05;
}
function App() {
const AddHeaderAndFooter = 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 the sample Word file into VFS
const inputFileName = "Sample.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
const inputImgFileName = "Header.png";
await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}/data/`);
const inputImgFileName_1 = "Footer.png";
await window.spire.FetchFileToVFS(inputImgFileName_1, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
let doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
let section = doc.Sections.get_Item(0);
// Insert header and footer
InsertHeaderAndFooter(section, inputImgFileName, inputImgFileName_1);
// Define the output file name
const outputFileName = "HeaderAndFooter.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Close();
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>Add Headers And Footers To Word Document</h1>
<button onClick={AddHeaderAndFooter}>
Generate
</button>
</div>
);
}
export default App;
Header and footer with image, text, and page numbers applied

Set a Different Header/Footer for the First Page
In many real-world scenarios, the first page (cover page) of a document needs different headers and footers than the rest of the pages, or even no headers and footers at all. Spire.Doc enables this by setting PageSetup.DifferentFirstPageHeaderFooter = true. The first page content is configured via HeadersFooters.FirstPageHeader / FirstPageFooter, while the remaining pages use HeadersFooters.Header / Footer.
function App() {
const DifferentFirstPage = 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 the sample file into VFS
let inputFileName = "MultiplePages.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
let inputImgFileName = "E-iceblue.png";
await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
let doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Get the section and enable a different first-page header/footer
let section = doc.Sections.get_Item(0);
section.PageSetup.DifferentFirstPageHeaderFooter = true;
// Set the first page header: insert an image aligned to the right
let paragraph1 = section.HeadersFooters.FirstPageHeader.AddParagraph();
paragraph1.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;
let headerimage = paragraph1.AppendPicture({ imgFile: inputImgFileName });
// Set the first page footer: centered text
let paragraph2 = section.HeadersFooters.FirstPageFooter.AddParagraph();
paragraph2.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
let FF = paragraph2.AppendText("First Page Footer");
FF.CharacterFormat.FontSize = 10;
// Set headers and footers for the other pages
let paragraph3 = section.HeadersFooters.Header.AddParagraph();
paragraph3.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
let NH = paragraph3.AppendText("Spire.Doc for JavaScript");
NH.CharacterFormat.FontSize = 10;
let paragraph4 = section.HeadersFooters.Footer.AddParagraph();
paragraph4.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
let NF = paragraph4.AppendText("E-iceblue");
NF.CharacterFormat.FontSize = 10;
// Define the output file name
const outputFileName = "DifferentFirstPage.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Close();
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 Different First Page Header And Footer</h1>
<button onClick={DifferentFirstPage}>
Generate
</button>
</div>
);
}
export default App;
The first page displays separate header and footer content, while the other pages use unified headers and footers.

Set Different Headers and Footers for Odd and Even Pages
For documents intended for duplex printing or book layout, it is common to use different headers and footers for odd and even pages — for example, odd-page headers show the chapter name aligned to the right, while even-page headers show the book title aligned to the left. Spire.Doc enables this by setting PageSetup.DifferentOddAndEvenPagesHeaderFooter = true, then configuring content via OddHeader / OddFooter and EvenHeader / EvenFooter respectively.
function App() {
const OddAndEvenHeaderFooter = 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 the sample file into VFS
let inputFileName = "MultiplePages.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
let doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section
let section = doc.Sections.get_Item(0);
// Enable different odd and even page headers/footers
section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = true;
// Add odd page header
let P3 = section.HeadersFooters.OddHeader.AddParagraph();
let OH = P3.AppendText("Odd Header");
P3.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
OH.CharacterFormat.FontName = "Arial";
OH.CharacterFormat.FontSize = 10;
// Add even page header
let P4 = section.HeadersFooters.EvenHeader.AddParagraph();
let EH = P4.AppendText("Even Header from E-iceblue Using Spire.Doc");
P4.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
EH.CharacterFormat.FontName = "Arial";
EH.CharacterFormat.FontSize = 10;
// Add odd page footer
let P2 = section.HeadersFooters.OddFooter.AddParagraph();
let OF = P2.AppendText("Odd Footer");
P2.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
OF.CharacterFormat.FontName = "Arial";
OF.CharacterFormat.FontSize = 10;
// Add even page footer
let P1 = section.HeadersFooters.EvenFooter.AddParagraph();
let EF = P1.AppendText("Even Footer from E-iceblue Using Spire.Doc");
EF.CharacterFormat.FontName = "Arial";
EF.CharacterFormat.FontSize = 10;
P1.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
// Define the output file name
const outputFileName = "OddAndEvenHeaderFooter_output.docx";
// Save the document
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// Release resources
doc.Close();
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 Odd And Even Page Headers And Footers</h1>
<button onClick={OddAndEvenHeaderFooter}>
Generate
</button>
</div>
);
}
export default App;
Different header and footer text is applied to odd and even pages.

FAQ
First-page header/footer does not display as expected
Cause: The DifferentFirstPageHeaderFooter property defaults to false. Editing FirstPageHeader or FirstPageFooter directly without enabling it has no effect.
Solution: Set the property to true before editing the first-page header/footer:
section.PageSetup.DifferentFirstPageHeaderFooter = true;
// Then edit FirstPageHeader / FirstPageFooter
Odd/even page settings do not take effect
Cause: The DifferentOddAndEvenPagesHeaderFooter property defaults to false. Editing OddHeader / EvenHeader directly without enabling it has no effect.
Solution: Enable the property before editing odd/even page content:
section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = true;
// Then edit OddHeader / EvenHeader / OddFooter / EvenFooter
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.
Merging and splitting table cells is one of the most common table editing operations in Word document development — whether creating report headers with cross-column titles or grouping products across rows, cell merging makes table structures clearer and more organized. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts and file 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.
Merge and Split Cells
A common task in real-world development is adjusting the structure of an existing table: merging adjacent cells into one, or splitting a single cell into multiple rows and columns. Spire.Doc provides ApplyHorizontalMerge, ApplyVerticalMerge, and SplitCell methods for horizontal merging, vertical merging, and splitting respectively. The workflow involves three steps: first, load font files and the target Word file into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, load the file, retrieve the target table, and call the merge or split methods; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const MergeAndSplitTableCell = 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 the sample Word file into VFS
let inputFileName = "TableSample.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
let doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
let section = doc.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
// Horizontal merge: merge columns 2 and 3 in row 6
table.ApplyHorizontalMerge(6, 2, 3);
// Vertical merge: merge rows 4 and 5 in column 2
table.ApplyVerticalMerge(2, 4, 5);
// Split cell: split the cell at row 8, column 3 into 2 rows and 2 columns
table.Rows.get_Item(8).Cells.get_Item(3).SplitCell(2, 2);
// Define the output file name
const outputFileName = "MergeAndSplitTableCell_output.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>Merge and Split Table Cells</h1>
<button onClick={MergeAndSplitTableCell}>
Generate
</button>
</div>
);
}
export default App;

Format Merged Cells
After merging cells, you typically need to format the merged area — setting font styles, alignment, and background colors — to improve table readability and visual appeal. The following example demonstrates how to create a product price table, merge the "Product" header cell and the version category cells on the left, and apply custom styling.
function AddTable(section) {
let wasmModule = window.wasmModule.spiredoc;
let table = section.AddTable({ showBorder: true });
table.ResetCells(4, 3);
// Table data
let dt = [["Product", "", "Inventory(kg)"],
["Fruit", "Apples", "150"],
["", "Grapes", "200"],
["", "Lemons", "100"]];
for (let r = 0; r < dt.length; r++) {
let dataRow = table.Rows.get_Item(r);
dataRow.Height = 20;
dataRow.HeightType = wasmModule.TableRowHeightType.Exactly;
for (let i = 0; i < dataRow.Cells.Count; i++) {
dataRow.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = wasmModule.Color.Empty;
}
for (let c = 0; c < dataRow.Cells.Count; c++) {
if (dt[r][c] !== "") {
let range = dataRow.Cells.get_Item(c).AddParagraph().AppendText(dt[r][c]);
range.CharacterFormat.FontName = "Arial";
}
}
}
return table;
}
function App() {
const FormatMergedCells = 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;
}
// Create a Word document
let doc = new wasmModule.Document();
let section = doc.AddSection();
// Add a table
let table = AddTable(section);
// Create a custom style
let style = new wasmModule.ParagraphStyle(doc);
style.Name = "Style";
style.CharacterFormat.TextColor = wasmModule.Color.get_DeepSkyBlue();
style.CharacterFormat.Italic = true;
style.CharacterFormat.Bold = true;
style.CharacterFormat.FontSize = 13;
doc.Styles.Add(style);
// Horizontal merge: merge columns 0 and 1 in row 0
table.ApplyHorizontalMerge(0, 0, 1);
// Apply the style
table.Rows.get_Item(0).Cells.get_Item(0).Paragraphs.get_Item(0).ApplyStyle(style.Name);
// Set vertical and horizontal alignment
table.Rows.get_Item(0).Cells.get_Item(0).CellFormat.VerticalAlignment = wasmModule.VerticalAlignment.Middle;
table.Rows.get_Item(0).Cells.get_Item(0).Paragraphs.get_Item(0).Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
// Vertical merge: merge rows 1, 2, and 3 in column 0
table.ApplyVerticalMerge(0, 1, 3);
// Apply the style
table.Rows.get_Item(1).Cells.get_Item(0).Paragraphs.get_Item(0).ApplyStyle(style.Name);
// Set vertical and horizontal alignment
table.Rows.get_Item(1).Cells.get_Item(0).CellFormat.VerticalAlignment = wasmModule.VerticalAlignment.Middle;
table.Rows.get_Item(1).Cells.get_Item(0).Paragraphs.get_Item(0).Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Left;
// Set column width
table.Rows.get_Item(1).Cells.get_Item(0).SetCellWidth(20, wasmModule.CellWidthType.Percentage);
// Define the output file name
const outputFileName = "FormatMergedCells_output.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>Format Merged Cells</h1>
<button onClick={FormatMergedCells}>
Generate
</button>
</div>
);
}
export default App;

Check Cell Merge Status
When working with tables created by others or generated by automated processes, you often need to identify which cells have been merged to avoid index-out-of-bounds errors. Spire.Doc provides two properties — CellFormat.VerticalMerge and Cell.GridSpan — to detect cell merge status: VerticalMerge indicates vertical merging, and GridSpan indicates the number of columns a cell spans horizontally.
function App() {
const CellMergeStatus = 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 the sample file into VFS
let inputFileName = "CellMergeStatus.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
let doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section and first table
let section = doc.Sections.get_Item(0);
let table = section.Tables.get_Item(0);
// Iterate through all cells to detect merge status
let stringBuidler = [];
for (let i = 0; i < table.Rows.Count; i++) {
let tableRow = table.Rows.get_Item(i);
for (let j = 0; j < tableRow.Cells.Count; j++) {
let tableCell = tableRow.Cells.get_Item(j);
let verticalMerge = tableCell.CellFormat.VerticalMerge;
let horizontalMerge = tableCell.GridSpan;
if (verticalMerge === wasmModule.CellMerge.None && horizontalMerge === 1) {
stringBuidler.push("Row " + i + ", cell " + j + ": ");
stringBuidler.push("This cell isn't merged.\n");
} else {
stringBuidler.push("Row " + i + ", cell " + j + ": ");
stringBuidler.push("This cell is merged.\n");
}
}
stringBuidler.push("\n");
}
// Define the output file name
const outputFileName = "CellMergeStatus_output.txt";
// Write the detection result to a text file
window.dotnetRuntime.Module.FS.writeFile(outputFileName, stringBuidler.join('\n'));
// Read the generated 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>Check Cell Merge Status</h1>
<button onClick={CellMergeStatus}>
Generate
</button>
</div>
);
}
export default App;

FAQ
How to verify if a cell merge was successful
Cause: Merge operations do not return a status value — you need to read cell properties to confirm.
Solution: Use CellFormat.VerticalMerge to check the vertical merge type (CellMerge.None means not merged), and Cell.GridSpan to check the number of columns spanned horizontally (a value of 1 means not merged):
let verticalMerge = tableCell.CellFormat.VerticalMerge;
let horizontalMerge = tableCell.GridSpan;
if (verticalMerge === wasmModule.CellMerge.None && horizontalMerge === 1) {
// Not merged
} else {
// Merged
}
Content lost after splitting a cell
Cause: When SplitCell splits a cell into multiple sub-cells, the original content remains in the first sub-cell by default.
Solution: Manually iterate through the sub-cells to redistribute content after splitting, or back up the cell text via the Paragraphs collection beforehand:
// Back up the content
let cell = table.Rows.get_Item(row).Cells.get_Item(col);
let text = cell.Paragraphs.get_Item(0).Text;
// Split into 2 rows and 2 columns
cell.SplitCell(2, 2);
// Write the content to the new cell
table.Rows.get_Item(row).Cells.get_Item(col).Paragraphs.get_Item(0).AppendText(text);
Index out of range when merging cells
Cause: ApplyHorizontalMerge(row, startCol, endCol) and ApplyVerticalMerge(col, startRow, endRow) use zero-based indexing. Passing indices that exceed the table's actual row or column count will throw an error.
Solution: Check the table dimensions before merging to ensure the end index does not exceed Rows.Count - 1 and Cells.Count - 1:
if (endCol < table.Rows.get_Item(row).Cells.Count && endRow < table.Rows.Count) {
table.ApplyHorizontalMerge(row, startCol, endCol);
}
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.