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 Original Word template and Excel data Output generated via mail merge Mail merge batch contract generation

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 Original Word template and Excel data Output generated via placeholder replacement Placeholder replacement contract generation


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

Configure it in code:

AIOptions options = new AIOptions();
options.SpireToken = key;

Headers 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:

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

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.

First page with different headers and footers from the rest of the document


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.

Odd and even pages with different headers and footers


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;

Word output after merging and splitting table cells


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;

Product price table with merged cells formatted


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;

Cell merge status detection results


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.

Page 9 of 346
page 9