PDF/A is an ISO-standardized long-term archival format that embeds fonts, color profiles, and metadata into a unified compliance level, ensuring documents remain faithfully reproducible for decades regardless of the PDF reader used. In contrast, standard PDF offers greater flexibility for everyday editing and content extraction. Real-world business often requires switching between these two formats: converting contracts to PDF/A for regulatory compliance during archiving, and restoring them to standard PDF for text extraction during audit review.

Spire.PDF for JavaScript performs bidirectional PDF/PDF/A conversion entirely in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) with no backend server required.

This article covers two core features:

For installation and project setup, refer to Integrating Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Convert PDF to PDF/A

The core of PDF/A archival conversion is to consolidate fonts, color profiles, and metadata in a standard PDF into ISO-compliant levels. Spire.PDF handles this in one step through the PdfStandardsConverter component, supporting multiple compliance levels including PDF/A-1a, PDF/A-1b, PDF/A-2a, PDF/A-2b, PDF/A-3a, and PDF/A-3b.

The conversion standards supported by PdfStandardsConverter and their use cases are as follows:

Method Standard Description
ToPdfA1B PDF/A-1b Based on PDF 1.4, guarantees visual appearance reproducibility only — the most commonly used archival level
ToPdfA1A PDF/A-1a Requires document tags and structure information on top of 1b, supports accessible reading
ToPdfA2A PDF/A-2a Based on PDF 1.7, requires tags and structure info, supports layers and transparency
ToPdfA2B PDF/A-2b PDF/A-2 basic conformance level, allows transparency, layers, and embedded OLE objects
ToPdfA3A PDF/A-3a Allows embedding XML, Excel, and other arbitrary format files as attachments on top of 2a
ToPdfA3B PDF/A-3b PDF/A-3 basic conformance level, supports embedding arbitrary format attachments
ToPdfX1A2001 PDF/X-1a:2001 Print exchange standard, suitable for publishing and printing workflows

The following example demonstrates converting a PDF to PDF/A-2B using ToPdfA2B:

function App() {
  const convertToPDFA = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check if the WASM module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'MovieCatalog.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfStandardsConverter
    let converter = new pdfModule.PdfStandardsConverter({ filePath: inputFileName });
    
    // Convert to PDF/A-2B format
    const outputFileName = 'ToPDFA_result.pdf';
    converter.ToPdfA2B({ filePath: outputFileName });

    // // Convert to PDF/A-1a
    // converter.ToPdfA1A({ filePath: outputFileName });

    // // Convert to PDF/A-2a
    // converter.ToPdfA2A({ filePath: outputFileName });

    // // Convert to PDF/A-2b
    // converter.ToPdfA2B({ filePath: outputFileName });

    // // Convert to PDF/A-3a
    // converter.ToPdfA3A({ filePath: outputFileName });

    // // Convert to PDF/A-3b
    // converter.ToPdfA3B({ filePath: outputFileName });

    // // Convert to PDF/X-1a:2001
    // converter.ToPdfX1A2001({ filePath: outputFileName });

    // Read the converted file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);

    // Release resources
    converter.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF To PDF/A</h1>
      <button onClick={convertToPDFA}>
        Generate
      </button>
    </div>
  );
}

export default App;

PDF/A output generated after conversion via PdfStandardsConverter

PDF/A output generated after conversion via PdfStandardsConverter


Convert PDF/A to PDF

PDF/A is the standard format for long-term archiving, but in everyday editing and content extraction scenarios, you may need to restore PDF/A back to standard PDF. Spire.PDF for JavaScript achieves this reverse conversion by loading the PDF/A document and copying content page by page into a new document, ensuring the output standard PDF is free of PDF/A compliance constraints.

function App() {
  const convertToNormalPDF = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check if the WASM module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'PDFA_Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Create a new PDF document to draw content onto
    let newDoc = new pdfModule.PdfNewDocument();
    newDoc.CompressionLevel = pdfModule.PdfCompressionLevel.None;

    // Iterate through each page in the original document
    for (let i = 0; i < doc.Pages.Count; i++) {
      let page = doc.Pages.get_Item(i);
      // Get the current page size
      let size = page.Size;

      // Add a new page with the same size and no margins
      let newPage = newDoc.Pages.Add({ size: size, margins: new pdfModule.PdfMargins() });

      // Draw the original page content onto the new page
      let template = page.CreateTemplate();
      let layoutWidget = new pdfModule.PdfLayoutWidget(template.H);
      layoutWidget.Draw({ page: newPage, x: 0, y: 0 });
      // page.CreateTemplate().Draw({page: newPage, x: 0, y: 0}); 
    }

    // Define the output file name
    const outputFileName = "PDFAToPdf_result.pdf";

    // Save the document to the specified path
    newDoc.Save(outputFileName);

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);

    // Release resources
    newDoc.Dispose();
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF/A To Normal PDF</h1>
      <button onClick={convertToNormalPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

Standard PDF output generated by creating a new document and copying pages

Standard PDF output generated by creating a new document and copying pages


FAQ

Converted PDF/A file size is much larger than the original

PDF/A requires all fonts used in the document to be fully embedded to ensure correct rendering on any device. If the original document uses non-embedded system fonts, the font data will be written completely into the output file during conversion, resulting in a larger file size. This is an inherent requirement of PDF/A compliance. To minimize file size, consider using font subsetting (embedding only the characters actually used) or compressing image content before generating the source PDF.

Can encrypted PDFs be converted to PDF/A?

Encrypted PDFs that require a password to open cannot be processed directly by PdfStandardsConverter. The password must be provided when loading the document.

The PdfStandardsConverter constructor supports a password parameter for converting password-protected PDFs to PDF/A:

// Create PdfStandardsConverter with password
let converter = new pdfModule.PdfStandardsConverter({ filePath: inputFileName, password: "123456" });
converter.ToPdfA2A({ filePath: outputFileName });
converter.Dispose();

"File not found" or "Invalid PDF format" error when loading PDF/A

PDF/A documents must first be converted via PdfStandardsConverter, or properly loaded into the virtual file system (VFS) via FetchFileToVFS. Common mistakes include passing the wrong file name or path, or executing subsequent operations before the file has finished loading. Verify that the file has been loaded into VFS via FetchFileToVFS and that the file name (including extension) matches exactly. Use await to ensure the file is ready before proceeding.


Get a Free License

Spire.PDF for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

Bookmarks are invisible positioning markers in Word documents that act as coordinates, precisely marking a location or a range of text. But the true value of bookmarks goes beyond positioning—by programmatically retrieving content within a bookmark range, replacing placeholder text, removing unwanted content, or inserting text, paragraphs, tables, and images at bookmark positions, developers can implement advanced document processing workflows such as automatic contract template filling, dynamic report data injection, and batch form content cleanup. The combination of "read, write, delete, and insert" operations around bookmark content forms the core of Word automation.

Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, handling bookmark content retrieval, replacement, deletion, and element insertion directly — all managed through a virtual file system (VFS) with no backend server required.

This article covers four 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.


Get Bookmark Content

Getting bookmark content is the prerequisite for any bookmark operation. After locating a bookmark with BookmarksNavigator, the GetBookmarkContent method returns the content within the bookmark range as a TextBodyPart object, which developers can iterate through its BodyItems collection to retrieve elements.

function App() {
  const bookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ContractTemplate_en.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("myBookmark");
    let textBodyPart = navigator.GetBookmarkContent();

    // Iterate through elements in the bookmark content and extract text
    let text = "";
    for (let i = 0; i < textBodyPart.BodyItems.Count; i++) {
      let item = textBodyPart.BodyItems.get_Item(i);
      if (item instanceof docModule.Paragraph) {
        for (let j = 0; j < item.ChildObjects.Count; j++) {
          let childObject = item.ChildObjects.get_Item(j);
          if (childObject instanceof docModule.TextRange) {
            text += childObject.Text;
          }
        }
      }
    }

    // Save as a .txt file
    const outputFileName = "GetBookmarkContent.txt";

    // Write the text file to VFS and trigger download
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, text);

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);

    // Release resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get Bookmark Content from Word Document</h1>
      <button onClick={bookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

Executing the code above extracts the text content from the bookmark "myBookmark" and saves it as a separate .txt file:

Extracted bookmark text content


Replace Bookmark Content

Replacing bookmark content is the most common operation in document template filling. After locating a bookmark with BookmarksNavigator, the ReplaceBookmarkContent method supports replacement with both plain text and complex elements like tables, making it ideal for placeholder replacement in contract generation, report filling, and similar scenarios.

function App() {
  const replaceBookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and Word file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'BookmarkSample.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("Bookmark1");

    // Replace the content of "书签1" — with text
    navigator.ReplaceBookmarkContent({ text: "This is the text that will replace the bookmark.", saveFormatting: true });

    // Continue to replace "书签2" — with a table
    navigator.MoveToBookmark("Bookmark2");

    // Create a table
    let table = new docModule.Table(doc, true);
    table.ResetCells(4, 5);

    // Create data and fill it into the table
    let dt = [
      ["City", "Province", "Population", "Area (km²)", "Abbrev."],
      ["Beijing", "Beijing", "21.89M", "16410", "BJ"],
      ["Shanghai", "Shanghai", "24.75M", "6340", "SH"],
      ["Guangzhou", "Guangdong", "18.67M", "7434", "GZ"]];
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < 5; j++) {
        table.Rows.get_Item(i).Cells.get_Item(j).AddParagraph().AppendText(dt[i][j]);
      }
    }

    // Create a TextBodyPart instance and add the table to it
    let part = new docModule.TextBodyPart({ doc: doc });
    part.BodyItems.Add(table);

    // Replace the current bookmark content with the TextBodyPart
    navigator.ReplaceBookmarkContent({ bodyPart: part });

    // Save as a new .docx file
    const outputFileName = "ReplaceBookmark.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Replace Bookmark Content in Word</h1>
      <button onClick={replaceBookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

This method supports replacing bookmark content with plain text or complex elements like tables. The bookmark marker itself is preserved after replacement, making it easy to locate again later. The figure below shows the result:

Bookmark content replaced with text and table


Delete Bookmark Content

Deleting bookmark content and removing a bookmark marker are two different operations. After locating a bookmark with BookmarksNavigator, calling DeleteBookmarkContent removes the text content within the bookmark range while preserving the bookmark marker itself for later refilling. If you only need to clear the content while keeping the positioning marker, this method is the preferred choice.

function App() {
  const deleteBookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ContractTemplate_en.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("myBookmark");

    // Delete bookmark content, keep the bookmark marker
    navigator.DeleteBookmarkContent(true);

    // Save as a new .docx file
    const outputFileName = "RemoveBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete Bookmark Content in Word</h1>
      <button onClick={deleteBookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

DeleteBookmarkContent removes only the text content within the bookmark range — the bookmark marker itself remains. Bookmarks.Remove, on the other hand, removes the bookmark marker, leaving the text within the range unaffected.

After execution, the text within the bookmark "myBookmark" is removed, but the bookmark marker stays in the document:

Bookmark content deleted — marker retained, content cleared


Insert Text, Paragraphs, Tables, and Images at a Bookmark

Spire.Doc supports flexibly inserting various types of document elements at bookmark positions. It provides InsertText, InsertParagraph, and InsertTable methods for inserting text, paragraphs, and tables. Elements can also be inserted based on the index of the bookmark start node within the paragraph's ChildObjects collection.

function App() {
  const insertElementsAtBookmark = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and Word file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'BookmarkSample1.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document object and load the file
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Move to the bookmark position
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("Bookmark1");

    // 1. Insert text
    navigator.InsertText("This is the inserted text content.", true);

    // 2. Insert paragraph
    let newParagraph = new docModule.Paragraph(doc);
    newParagraph.AppendText("This is the inserted paragraph content.")
    navigator.MoveToBookmark("Bookmark2");
    navigator.InsertParagraph(newParagraph);

    // 3. Insert table — 2 rows, 3 columns
    let table = new docModule.Table(doc, true);
    table.ResetCells(2, 3);
    table.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("Name");
    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText("Quantity");
    table.Rows.get_Item(0).Cells.get_Item(2).AddParagraph().AppendText("Note");
    table.Rows.get_Item(1).Cells.get_Item(0).AddParagraph().AppendText("Product A");
    table.Rows.get_Item(1).Cells.get_Item(1).AddParagraph().AppendText("100");
    table.Rows.get_Item(1).Cells.get_Item(2).AddParagraph().AppendText("In Stock");

    navigator.MoveToBookmark("Bookmark3");
    navigator.InsertTable(table);

    // 4. Insert image
    const imageFileName = 'pic.png';
    await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);
    let picture = new docModule.DocPicture(doc);
    picture.LoadImage(imageFileName);
    picture.Width = 100;
    picture.Height = 200;

    navigator.MoveToBookmark("Bookmark4");
    // Get the bookmark start node
    let start = navigator.CurrentBookmark.BookmarkStart;
    // Get the paragraph containing the bookmark
    let bookmarkPara = start.OwnerParagraph;
    // Get the index of the bookmark start node in the paragraph
    let startIndex = bookmarkPara.ChildObjects.IndexOf(start);
    // Insert the image after the bookmark start node
    bookmarkPara.ChildObjects.Insert(startIndex + 1, picture);

    // Save as a .docx file
    const outputFileName = "InsertToBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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 Document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Insert Elements at Bookmark Position</h1>
      <button onClick={insertElementsAtBookmark}>
        Generate
      </button>
    </div>
  );
}

export default App;

The figure below shows the generated document with text, paragraph, table, and image inserted at bookmark positions:

Text, paragraph, table, and image inserted at bookmark positions


FAQ

Formatting (font, size, color) is lost after replacing bookmark content — how to keep it?

ReplaceBookmarkContent replaces with plain text by default, discarding the original formatting. To preserve the bookmark's existing formatting, pass saveFormatting: true:

navigator.ReplaceBookmarkContent({ text: "New content", saveFormatting: true });

The replacement text will then inherit the original font, size, color, and other formatting from the bookmark.

How to batch process multiple bookmarks in a document?

Iterate through the doc.Bookmarks collection, locating and operating on each bookmark one by one:

for (let i = 0; i < doc.Bookmarks.Count; i++) {
    let bookmark = doc.Bookmarks.get_Item(i);
    navigator.MoveToBookmark(bookmark.Name);
    // Perform replace, delete, or insert operations
}

What's the difference between DeleteBookmarkContent and removing a bookmark marker?

  • DeleteBookmarkContent: Clears only the content within the bookmark range. The bookmark marker stays in the document, so you can still locate it by name and fill in new content later.
  • Bookmarks.Remove: Removes the bookmark marker itself. The content within the bookmark range is unaffected, but the bookmark name disappears and can no longer be located.

Choose the appropriate operation based on your needs: use DeleteBookmarkContent if you need to keep the "placeholder" capability, or remove the marker if the bookmark is no longer needed.

When inserting multiple elements at the same bookmark, why does only the last one take effect?

Methods like InsertText, InsertParagraph, and InsertTable insert based on the bookmark's current position. When inserting multiple times at the same bookmark, subsequent insertions may overwrite or shift previously inserted content. It is recommended to use separate bookmarks for each insertion, or re-locate the bookmark after each insert before proceeding with the next operation.


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.

Bookmarks are invisible positioning markers in Word documents that act as coordinates, precisely marking a location or a range of text. Whether it's a fill-in area in a contract template, a key section to jump to in a long document, or a data insertion point when generating reports in batch, bookmarks are the critical anchor behind these operations. Developers can use bookmarks for dynamic content filling, navigation, content extraction, and other advanced features, making bookmark management one of the most commonly used capabilities in Word automation.

Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, handling bookmark creation, navigation, and deletion directly — all managed 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 a Bookmark to a Paragraph

To add a bookmark in an existing document, use AppendBookmarkStart and AppendBookmarkEnd to mark the bookmark region on a paragraph. You can add bookmark markers to existing paragraphs or append a new paragraph with a bookmark. Spire.Doc also supports nested bookmarks for building hierarchical structures.

function App() {
  const createBookmarkInWord = 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 Word file into VFS
    const inputFileName = 'ChinaTravelGuide.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 section and add bookmarks
    let section = doc.Sections.get_Item(0);

    AddBookmark(section);

    // Save as a .docx file
    const outputFileName = "AddBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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
    doc.Dispose();
  };

  function AddBookmark(section) {
    // Bookmark 1: add bookmark markers around existing paragraphs
    let paraStart = section.Paragraphs.get_Item(1); 
    let paraEnd = section.Paragraphs.get_Item(3); 

    paraStart.AppendBookmarkStart("Bookmark1");  
    paraEnd.AppendBookmarkEnd("Bookmark1"); 

    // Bookmark 2: add a new paragraph with a bookmark
    let paragraph = section.AddParagraph(); 
    paragraph.AppendBookmarkStart("Bookmark2"); 
    paragraph.AppendText("This is a new paragraph");
    paragraph.AppendBookmarkEnd("Bookmark2"); 
  }

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Bookmark in Word</h1>
      <button onClick={createBookmarkInWord}>
        Generate
      </button>
    </div>
  );
}

export default App;

Bookmarks added to the generated Word document

Bookmarks added to the generated Word document


Add a Bookmark to Selected Text

To add a bookmark to specific text within an existing paragraph, first locate the text with FindAllString, create bookmark objects using the BookmarkStart and BookmarkEnd constructors, then insert the start marker before and the end marker after the matched TextRange via ChildObjects.Insert.

function App() {
  const addBookmarkForMatchedText = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ChinaTravelGuide.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Find all occurrences of "Street" in the document
    let textSelections = doc.FindAllString('Street', false, true);

    // Iterate over each match and insert bookmark start/end markers
    for (let i = 0; i < textSelections.length; i++) {

      // Create bookmark start and end objects (named "Bookmark_0", "Bookmark_1", ...)
      let start = new docModule.BookmarkStart(doc, "Bookmark_" + i);
      let end = new docModule.BookmarkEnd(doc, "Bookmark_" + i);

      let selection = textSelections[i];

      // Get the TextRange of the matched text
      let textRange = selection.GetAsOneRange();

      // Get the paragraph containing the matched text
      let para = textRange.OwnerParagraph;

      // Get the index of the TextRange within the paragraph's child objects
      let index = para.ChildObjects.IndexOf(textRange);

      // Insert the bookmark start before the TextRange and the bookmark end after it
      para.ChildObjects.Insert(index, start);
      para.ChildObjects.Insert(index + 2, end);
    }

    // Save as a new .docx file
    const outputFileName = "AddBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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 document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Bookmarks for Specific Text in Word Documents</h1>
      <button onClick={addBookmarkForMatchedText}>
        Generate
      </button>
    </div>
  );
}

export default App;

This approach is ideal for scenarios where you need to add positioning markers on top of an existing document, such as marking fill-in areas in a completed contract. The figure below shows the result after execution:

Bookmarks added for specific text in the generated Word document


Remove a Bookmark

Removing a bookmark only removes the bookmark markers themselves — the text content within the bookmark range is preserved. Retrieve the bookmark object from the document.Bookmarks collection, then call the Remove method to delete it.

function App() {
  const deleteBookmark = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'AddBookmark.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Get the bookmark by name
    let bookmark = doc.Bookmarks.get_Item("Bookmark_1");

    // // Get the bookmark by index
    // let bookmark = doc.Bookmarks.get_Item(0);

    // Remove the bookmark (keep its content)
    doc.Bookmarks.Remove(bookmark);

    // Save as a new .docx file
    const outputFileName = "DeleteBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Read the generated file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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 document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete Bookmark in Word Document</h1>
      <button onClick={deleteBookmark}>
        Generate
      </button>
    </div>
  );
}

export default App;

After the bookmark is removed, its markers disappear from the document, but the text within the bookmark range is preserved.

Document after bookmark markers are removed


FAQ

Duplicate bookmark name error

Cause: Bookmark names must be unique within a Word document. Adding a bookmark with a duplicate name causes an error.

Solution: Check whether the name already exists before adding the bookmark:

if (document.Bookmarks.FindByName("MyBookmark") === null) {
    paragraph.AppendBookmarkStart("MyBookmark");
    paragraph.AppendText("Content");
    paragraph.AppendBookmarkEnd("MyBookmark");
}

What is the difference between removing a bookmark and deleting its content?

Cause: Spire.Doc's Bookmarks.Remove only removes the bookmark markers (start and end), leaving the text content between them untouched.

Solution: Choose the appropriate operation based on your needs:

// Remove only the bookmark markers, keep the text
document.Bookmarks.Remove(bookmark);

// Remove the bookmark and its content (via BookmarksNavigator)
let navigator = new docModule.BookmarksNavigator(doc);
navigator.MoveToBookmark("MyBookmark");
navigator.DeleteBookmarkContent();

Do AppendBookmarkStart and AppendBookmarkEnd have to be on the same paragraph?

Cause: The start and end markers can be on different paragraphs — the "Bookmark1" example in the code above demonstrates cross-paragraph usage. The key constraint is that the document object structure within the bookmark range must remain intact. Bookmarks cannot span across table cells, since cells are independent containers and doing so may cause the bookmark to be unrecognized.

Solution: If the bookmark range crosses a table cell boundary, adjust the start or end position so that the bookmark closes within the same cell.


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.

Replacing placeholders in documents with HTML content or paragraphs from another document is a highly practical need in document automation — for example, inserting rich HTML content authored in a WYSIWYG editor into placeholder positions in a Word template, or extracting specific paragraphs from a standard clause library and replacing corresponding placeholders in a contract template. Spire.Doc for JavaScript handles such replacement operations entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts and document 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.


Replace Placeholder with HTML

Replacing a placeholder with HTML involves three stages: first, load the font files, the HTML file, and the target Word document into the WASM virtual file system via FetchFileToVFS; then, create a temporary Section, render the HTML string into document objects using AppendHTML, collect them into a replacement list, find all [#placeholder] occurrences with FindAllString, sort the matched positions, and insert the replacement content one by one via ChildObjects.Insert while removing the original text; finally, remove the temporary Section, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  // Define the placeholder replacement logic
  function ReplacedWithHTML(location, replacement) {
    let textRange = location.Text;
    let index = location.Index;
    let paragraph = location.Owner;
    let sectionBody = paragraph.OwnerTextBody;
    let paragraphIndex = sectionBody.ChildObjects.IndexOf(paragraph);

    let replacementIndex = -1;
    if (index === 0) {
      paragraph.ChildObjects.RemoveAt(0);
      replacementIndex = sectionBody.ChildObjects.IndexOf(paragraph);
    } else if (index === paragraph.ChildObjects.Count - 1) {
      paragraph.ChildObjects.RemoveAt(index);
      replacementIndex = paragraphIndex + 1;
    } else {
      let paragraph1 = paragraph.Clone();
      while (paragraph.ChildObjects.Count > index) {
        paragraph.ChildObjects.RemoveAt(index);
      }
      let i = 0;
      let count = index + 1;
      while (i < count) {
        paragraph1.ChildObjects.RemoveAt(0);
        i += 1;
      }
      sectionBody.ChildObjects.Insert(paragraphIndex + 1, paragraph1);
      replacementIndex = paragraphIndex + 1;
    }

    for (let i = 0; i <= replacement.length - 1; i++) {
      sectionBody.ChildObjects.Insert(replacementIndex + i, replacement[i].Clone());
    }
  }

  function TextRangeLocation(TextRange) {
    this.Text = TextRange;
    this.Owner = this.Text.OwnerParagraph;
    this.Index = this.Owner.ChildObjects.IndexOf(this.Text);
    this.CompareTo = function (other) {
      return -(this.Index - other.Index);
    };
  }

  const ReplaceWithHTML = 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 font file into the virtual file system (VFS)
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the HTML file and Word document into VFS
    let HTMLName = 'InputHtml.txt';
    await window.spire.FetchFileToVFS(HTMLName, '', `${process.env.PUBLIC_URL}/data/`);
    const HTML = window.dotnetRuntime.Module.FS.readFile(HTMLName);

    let inputFileName = 'ReplaceWithHtml.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a temporary Section and render the HTML
    let replacement = [];
    let tempSection = doc.AddSection();
    let par = tempSection.AddParagraph();
    const decoder = new TextDecoder('utf-8');
    const HTMLString = decoder.decode(HTML);
    par.AppendHTML(HTMLString);

    // Collect the rendered document objects
    for (let i = 0; i < tempSection.Body.ChildObjects.Count; i++) {
      let docObj = tempSection.Body.ChildObjects.get_Item(i);
      replacement.push(docObj);
    }

    // Find all placeholders and sort
    let selections = doc.FindAllString('[#placeholder]', false, true);
    let locations = [];
    for (let selection of selections) {
      locations.push(new TextRangeLocation(selection.GetAsOneRange()));
    }
    locations.sort();

    // Replace one by one
    for (let location of locations) {
      ReplacedWithHTML(location, replacement);
    }

    // Remove the temporary Section
    doc.Sections.Remove(tempSection);

    // Define the output file name and save
    const outputFileName = 'ReplaceWithHtml_output.docx';
    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>Replace Placeholder with HTML in a Word Document</h1>
      <button onClick={ReplaceWithHTML}>
        Generate
      </button>
    </div>
  );
}

export default App;

The [#placeholder] placeholders in the document are replaced with rich text content rendered from HTML

The [#placeholder] placeholders in the document are replaced with rich text content rendered from HTML


Replace Placeholder with Paragraphs from Another Document

Replacing a placeholder with paragraphs from another document involves three stages: first, load the font files and two Word documents into the WASM virtual file system via FetchFileToVFS; then, load the main document and the source document separately, use FindAllPattern with a regular expression to find placeholders (such as [MY_DOCUMENT]), iterate through all Sections and Paragraphs of the source document, insert each paragraph into the corresponding position in the main document using ChildObjects.Insert, and finally remove the original placeholder text; lastly, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  const ReplaceContentWithDoc = 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 two Word documents into VFS
    let inputFileName1 = 'ReplaceContentWithDoc.docx';
    await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}/data/`);

    let inputFileName2 = 'Insert.docx';
    await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the main document
    let document1 = new docModule.Document();
    document1.LoadFromFile(inputFileName1);

    // Load the source document (contains paragraphs to insert)
    let document2 = new docModule.Document();
    document2.LoadFromFile(inputFileName2);

    // Get the first Section of the main document
    let section1 = document1.Sections.get_Item(0);

    // Create a regex to find the placeholder
    let regex = new docModule.Regex('\\[MY_DOCUMENT\\]', docModule.RegexOptions.None);

    // Find all matching placeholders
    let textSections = document1.FindAllPattern({ pattern: regex });

    // Iterate through each match
    for (let i = 0; i < textSections.length; i++) {
      let selection = textSections[i];
      let para = selection.GetAsOneRange().OwnerParagraph;
      let textRange = selection.GetAsOneRange();
      let index = section1.Body.ChildObjects.IndexOf(para);

      // Insert all paragraphs from the source document at the placeholder position
      for (let i = 0; i < document2.Sections.Count; i++) {
        let section2 = document2.Sections.get_Item(i);
        for (let j = 0; j < section2.Paragraphs.Count; j++) {
          let paragraph = section2.Paragraphs.get_Item(j);
          section1.Body.ChildObjects.Insert(index++, paragraph.Clone());
        }
      }

      // Remove the original placeholder text
      para.ChildObjects.Remove(textRange);
    }

    // Define the output file name and save
    const outputFileName = 'ReplaceContentWithDoc_output.docx';
    document1.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    document1.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>Replace Placeholder with Paragraphs from Another Document</h1>
      <button onClick={ReplaceContentWithDoc}>
        Generate
      </button>
    </div>
  );
}

export default App;

The [MY_DOCUMENT] placeholder in the main document is replaced with all paragraphs from the source document

The [MY_DOCUMENT] placeholder in the main document is replaced with all paragraphs from the source document


FAQ

HTML content formatting is not displayed correctly

Cause: The AppendHTML method supports a limited range of HTML tags, only recognizing basic block-level and inline tags (such as <p>, <b>, <i>, <table>, etc.). Complex CSS styles, JavaScript code, or HTML5-specific tags are ignored.

Solution: Ensure the input HTML uses only basic tags and defines formatting through inline styles (such as style="color:red") rather than CSS class names:

<p style="font-size:14pt; color:#2E75B6;">This is blue heading text</p>
<ul><li>Item one</li><li>Item two</li></ul>

Inserted paragraph order does not match expectations

Cause: When replacing multiple placeholders, the matched positions are not sorted before processing. Replacing sequentially from beginning to end causes the indices of subsequent positions to shift, leading to paragraphs being inserted at incorrect locations.

Solution: Sort all matched positions in descending order by index (replacing from the end of the document backward), or track the original index offset for each position:

let locations = [];
for (let selection of selections) {
  locations.push(new TextRangeLocation(selection.GetAsOneRange()));
}
locations.sort(); // Descending order, replace from back to front

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.

Replacing specific placeholder text with images or tables is a common requirement in real-world development — such as replacing a "(Seal)" marker at the end of a contract with a company stamp image, or swapping a data summary placeholder with a statistics table. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts, images, and document 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.


Replace Text with Image

Replacing text with an image involves three steps: first, load the font files, the target image, and the Word document into the WASM virtual file system via FetchFileToVFS; then call FindAllString to locate all matching text, iterate through each match, load the image with DocPicture, insert it using ChildObjects.Insert, and remove the original text via ChildObjects.Remove; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  const ReplaceWithImage = 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 image and Word document into VFS
    let pngName = 'E-iceblue.png';
    await window.spire.FetchFileToVFS(pngName, '', `${process.env.PUBLIC_URL}/data/`);

    let inputFileName = 'Template.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Find all matching text
    let selections = doc.FindAllString('E-iceblue', true, true);

    // Iterate through each match and replace text with the image
    for (let i = 0; i < selections.length; i++) {
      // Create a DocPicture object and load the image
      let pic = new docModule.DocPicture(doc);
      pic.LoadImage(pngName);
      let selection = selections[i];
      // Get the current text range
      let range = selection.GetAsOneRange();
      // Get the index of the TextRange in its owner paragraph's ChildObjects
      let index = range.OwnerParagraph.ChildObjects.IndexOf(range);
      // Insert the image at the TextRange position
      range.OwnerParagraph.ChildObjects.Insert(index, pic);
      // Remove the original TextRange
      range.OwnerParagraph.ChildObjects.Remove(range);
    }

    // Define the output file name and save
    const outputFileName = 'ReplaceWithImage_output.docx';
    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>Replace text with image in Word document</h1>
      <button onClick={ReplaceWithImage}>
        Generate
      </button>
    </div>
  );
}

export default App;

Target text in the document is found and replaced with the specified image

Target text in the document is found and replaced with the specified image


Replace Text with Table

Replacing text with a table involves three steps: first, load the font files and the target Word document into the WASM virtual file system via FetchFileToVFS; then call FindString to locate the target text, retrieve the text range with GetAsOneRange, get the paragraph index via OwnerTextBody.ChildObjects, create a new table, remove the original paragraph with ChildObjects.Remove, and insert the table at the same position with ChildObjects.Insert; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  const ReplaceTextWithTable = 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 font file into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the Word document into VFS
    let inputFileName = 'Template_Docx_1.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 section
    let section = doc.Sections.get_Item(0);

    // Find the target text
    let selection = doc.FindString('Christmas Day, December 25', true, true);

    // Get the text range and its owner paragraph
    let range = selection.GetAsOneRange();
    let paragraph = range.OwnerParagraph;

    // Get the text body and calculate the paragraph index
    let body = paragraph.OwnerTextBody;
    let index = body.ChildObjects.IndexOf(paragraph);

    // Create a 3x3 table
    let table = section.AddTable(true);
    table.ResetCells(3, 3);

    // Remove the original paragraph and insert the table at the same position
    body.ChildObjects.Remove(paragraph);
    body.ChildObjects.Insert(index, table);

    // Define the output file name and save
    const outputFileName = 'ReplaceTextWithTable_output.docx';
    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>Replace text with table in Word document</h1>
      <button onClick={ReplaceTextWithTable}>
        Generate
      </button>
    </div>
  );
}

export default App;

The target paragraph is removed and a table is inserted at the same position

The target paragraph is removed and a table is inserted at the same position


FAQ

Image does not appear in the document after insertion

Cause: The image file was not loaded into the WASM virtual file system, or the FetchFileToVFS path is incorrect, causing DocPicture.LoadImage to fail when reading the image data from VFS.

Solution: Ensure the image file is properly loaded into VFS via FetchFileToVFS before calling LoadImage, and verify the file name and path match:

await window.spire.FetchFileToVFS(
  'E-iceblue.png', '', `${process.env.PUBLIC_URL}/data/`
);

Table is inserted at the wrong position

Cause: The paragraph index obtained via OwnerTextBody.ChildObjects.IndexOf is inaccurate, or the selected text spans multiple paragraphs, causing an index offset that places the table in the wrong location.

Solution: Verify that the text searched by FindString resides within a single paragraph, then use GetAsOneRange to obtain the correct OwnerParagraph before retrieving its index in OwnerTextBody.ChildObjects:

let range = selection.GetAsOneRange();
let paragraph = range.OwnerParagraph;
let body = paragraph.OwnerTextBody;
let index = body.ChildObjects.IndexOf(paragraph);

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.

Text replacement is one of the most common operations in Word document processing — whether it's batch-replacing placeholders in contracts, standardizing terminology, or normalizing text of a specific pattern, the find-and-replace feature handles it efficiently. Spire.Doc for JavaScript processes Word documents directly in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts and file resources — 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.


Replace Specific Text

Replacing specific text is the most basic text operation — it replaces a word or phrase in a document with another string, with options for case sensitivity and whole-word matching. The core process involves three steps: first, load font files and the target Word document into the WASM virtual file system via FetchFileToVFS; then instantiate a Document, load the file, and call the Replace method to match a specified string and replace it with new text, controlling the matching rules through caseSensitive and wholeWord parameters; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {

  const ReplaceWithText = 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 sample file into VFS
        let inputFileName = 'Sample.docx';
        await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

        // Create a new document
        const doc = new docModule.Document();

        // Load the document from the virtual file system
        doc.LoadFromFile(inputFileName);

        // Replace text: "word" → "ReplacedText", case-insensitive, whole-word match
        doc.Replace({ matchString: 'word', newValue: 'ReplacedText', caseSensitive: false, wholeWord: true });

        // Define the output file name and save
        const outputFileName = 'ReplaceWithText_output.docx';
        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>Replace text in a Word document.</h1>
      <button onClick={ReplaceWithText}>
        Generate
      </button>
    </div>
    );
  };

export default App;

Target strings in the document are uniformly replaced with the new content after using the Replace method

Target strings in the document are uniformly replaced with the new content after using the Replace method


Replace Text Using Regex

When you need to match text with variable formatting, regex is the most powerful tool — for example, replacing all hashtag labels starting with # with a fixed string. The core process involves three steps: first, load font files and the target Word document into the WASM virtual file system via FetchFileToVFS; then create a Regex object to define the matching pattern, and call the Replace method to uniformly replace all matched text with the specified content; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {

  const ReplaceWithText = 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 sample file into VFS
        let inputFileName = 'Sample.docx';
        await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

        // Create a new document
        const doc = new docModule.Document();

        // Load the document from the virtual file system
        doc.LoadFromFile(inputFileName);

        // Create a regex pattern to match whole words
        let regex = new docModule.Regex('\\bword\\b', docModule.RegexOptions.None);

        // Replace text using the regex
        doc.Replace(regex, 'Spire.Doc');

        // Define the output file name and save
        const outputFileName = 'ReplaceTextByRegex_output.docx';
        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>Replace text in a Word document.</h1>
      <button onClick={ReplaceWithText}>
        Generate
      </button>
    </div>
    );
  };

export default App;

All strings matching the pattern are uniformly replaced after regex-based text replacement

All strings matching the pattern are uniformly replaced after regex-based text replacement


FAQ

Text replacement does not take effect (text is not replaced)

Cause: The caseSensitive or wholeWord parameters are set incorrectly, causing the match to fail — the actual text does not meet the matching criteria.

Solution: Check whether the case matches, or set caseSensitive to false to ignore case, and wholeWord to false to match partial words:

doc.Replace({ matchString: 'word', newValue: 'ReplacedText', caseSensitive: false, wholeWord: false });

Regex pattern does not match the target content

Cause: Special characters (such as #, \) in the regex pattern are not properly escaped, causing the match to fail.

Solution: Ensure that special characters in the regex pattern are correctly escaped. For example, when matching #tag-style text:

let regex = new wasmModule.Regex('#\\S+', wasmModule.RegexOptions.None);

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.

Replacing specific text with field codes or another document's content is a highly practical need in document automation — such as replacing a "current date" placeholder with a DATE field that automatically updates to the system date each time the document is opened, or swapping a "terms" placeholder in a contract template with detailed clause content from another Word document for modular document assembly. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts and document 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.


Replace Text with a Field

Replacing text with a field involves three steps: first, load the font files and the target Word document into the WASM virtual file system via FetchFileToVFS; then call FindString to locate the target text, retrieve its paragraph and position index, create a Field object with the desired field type (such as FieldDate), sequentially insert Field, FieldMark(FieldSeparator), and FieldMark(FieldEnd) into the paragraph to construct a complete field structure, and finally remove the original text; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  const ReplaceTextWithField = 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 Word document into VFS
    let inputFileName = 'ReplaceTextWithField.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Find the target text
    let selection = doc.FindString({
      stringValue: 'summary',
      caseSensitive: false,
      wholeWord: true,
    });

    // Get the text range and its owner paragraph
    let textRange = selection.GetAsOneRange();
    let ownParagraph = textRange.OwnerParagraph;
    let rangeIndex = ownParagraph.ChildObjects.IndexOf(textRange);

    // Create a DATE field
    let eqField = new docModule.Field(doc);
    eqField.Type = docModule.FieldType.FieldDate;
    eqField.Code = 'DATE \\@ "yyyyMMdd "';

    // Insert Field, FieldSeparator, and FieldEnd in sequence
    ownParagraph.ChildObjects.Insert(rangeIndex, eqField);

    let mark = new docModule.FieldMark(doc, docModule.FieldMarkType.FieldSeparator);
    ownParagraph.ChildObjects.Insert(rangeIndex + 1, mark);

    let end = new docModule.FieldMark(doc, docModule.FieldMarkType.FieldEnd);
    ownParagraph.ChildObjects.Insert(rangeIndex + 2, end);

    // Remove the original text
    ownParagraph.ChildObjects.Remove(textRange);

    // Define the output file name and save
    const outputFileName = 'ReplaceTextWithField_output.docx';
    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>Replace text with field in Word document</h1>
      <button onClick={ReplaceTextWithField}>
        Generate
      </button>
    </div>
  );
}

export default App;

The target text is replaced with a DATE field that automatically displays the current system date when the document is opened

The target text is replaced with a DATE field that automatically displays the current system date when the document is opened


Replace Text with Another Document

Replacing text with another document involves three steps: first, load the font files and two Word documents (the main document and the replacement content document) into the WASM virtual file system via FetchFileToVFS; then instantiate two Document objects to load both documents, call the Replace method on the main document with the matchDoc parameter pointing to the replacement document object, substituting the matching text with the full content of that document; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

import React, { useState } from 'react';

function App() {
  const ReplaceWithDocument = 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 two Word documents into VFS
    let inputFileName1 = 'Text2.docx';
    await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}/data/`);

    let inputFileName2 = 'Text1.docx';
    await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the main document
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName1);

    // Load the replacement document
    let replaceDoc = new docModule.Document();
    replaceDoc.LoadFromFile(inputFileName2);

    // Replace the specified text with the content of another document
    doc.Replace({
      matchString: 'Document1',
      matchDoc: replaceDoc,
      caseSensitive: false,
      wholeWord: true,
    });

    // Define the output file name and save
    const outputFileName = 'ReplaceWithDocument_output.docx';
    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>Replace text with another document in Word</h1>
      <button onClick={ReplaceWithDocument}>
        Generate
      </button>
    </div>
  );
}

export default App;

The placeholder text in the main document is replaced with the full content of another document

The placeholder text in the main document is replaced with the full content of another document


FAQ

Field value does not appear in the document after insertion

Cause: The Field, FieldMark(FieldSeparator), and FieldMark(FieldEnd) are not inserted in the correct order, breaking the integrity of the field structure and preventing Word from properly recognizing and evaluating the field value.

Solution: Ensure that Field, FieldSeparator, and FieldEnd are inserted sequentially with consecutive indices:

ownParagraph.ChildObjects.Insert(rangeIndex, eqField);
ownParagraph.ChildObjects.Insert(rangeIndex + 1, mark);
ownParagraph.ChildObjects.Insert(rangeIndex + 2, end);

Replacement with another document does not work

Cause: The document object passed via the matchDoc parameter in the Replace method was not properly loaded into VFS, or the file path/name does not match what was used in FetchFileToVFS, causing LoadFromFile to fail to locate the target file.

Solution: Ensure both documents are loaded into VFS via FetchFileToVFS, and create separate Document objects that successfully load before calling Replace:

let doc = new docModule.Document();
doc.LoadFromFile('Text2.docx');

let replaceDoc = new docModule.Document();
replaceDoc.LoadFromFile('Text1.docx');

doc.Replace({
  matchString: 'Document1',
  matchDoc: replaceDoc,
  caseSensitive: false,
  wholeWord: true,
});

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.

In practical document layout, a textbox is an ideal container for standalone content — it can be positioned freely without being constrained by the page flow, making it perfect for sidebars, pull quotes, product diagrams, and more. Inserting images or tables into a textbox is a common requirement for achieving rich layouts. Spire.Doc for JavaScript processes Word documents directly in the browser via WebAssembly, using a virtual file system (VFS) to manage fonts and file resources — 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.


Insert Image into Textbox

Filling a textbox with a picture is a common technique for creating product labels, business cards, or promotional materials. Spire.Doc creates a textbox via the AppendTextBox method, then fills its background with a picture using FillEfects.SetPicture. The core process involves three steps: first, load font files and the target image into the WASM virtual file system via FetchFileToVFS; then create a document, add a textbox with position properties, and apply the picture to the textbox through fill effects; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

function App() {
  const InsertImageIntoTextBox = 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 image file into VFS
    const imageFileName = 'Spire.Doc.png';
    await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a document
    const doc = new docModule.Document();

    // Add a section
    let section = doc.AddSection();

    // Add a paragraph
    let paragraph = section.AddParagraph();

    // Append a 220x220 textbox to the paragraph
    let tb = paragraph.AppendTextBox(220, 220);

    // Set textbox position
    tb.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
    tb.Format.HorizontalPosition = 50;
    tb.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
    tb.Format.VerticalPosition = 50;

    // Set the textbox fill type to Picture
    tb.Format.FillEfects.Type = docModule.BackgroundType.Picture;

    // Fill the textbox with the image
    tb.Format.FillEfects.SetPicture(imageFileName);

    // Define output file name and save
    const outputFileName = "InsertImageIntoTextBox_output.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.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>Insert Image into a Word Textbox</h1>
      <button onClick={InsertImageIntoTextBox}>
        Generate
      </button>
    </div>
  );
}

export default App;

Image filled into the textbox, automatically scaled to fit

Image filled into the textbox, automatically scaled to fit


Insert Table into Textbox

Embedding structured table data in sidebars or reference areas is a common requirement in technical documents and reports. Spire.Doc supports creating tables within a textbox and adding them directly to the textbox body via the textbox.Body.AddTable method. Compared to creating tables in the main document body, tables inside a textbox can be positioned independently without being affected by page layout. The core process involves three steps: first, load font files into the WASM virtual file system via FetchFileToVFS; then create a document and a textbox, add a table via textbox.Body.AddTable with specified row and column counts, fill data row by row, and apply styles; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

function App() {
  const InsertTableIntoTextBox = 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 font files into VFS
    await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a document
    const doc = new docModule.Document();

    // Add a section
    let section = doc.AddSection();

    // Add a paragraph
    let paragraph = section.AddParagraph();

    // Add a 300x150 textbox
    let textbox = paragraph.AppendTextBox(300, 150);

    // Set textbox position
    textbox.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
    textbox.Format.HorizontalPosition = 140;
    textbox.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
    textbox.Format.VerticalPosition = 50;

    // Add a title paragraph in the textbox
    let textboxParagraph = textbox.Body.AddParagraph();
    let textboxRange = textboxParagraph.AppendText("Table 1");
    textboxRange.CharacterFormat.FontName = "Microsoft YaHei";

    // Insert a table into the textbox
    let table = textbox.Body.AddTable({ showBorder: true });

    // Specify the number of rows and columns
    table.ResetCells(4, 4);

    let data = [
      ["Name", "Age", "Gender", "ID"],
      ["John", "28", "Male", "0023"],
      ["Jane", "30", "Male", "0024"],
      ["Wang Wu", "26", "Female", "0025"]
    ];
    // Populate data into the table
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < 4; j++) {
        let tableRange = table.Rows.get_Item(i).Cells.get_Item(j).AddParagraph().AppendText(data[i][j]);
        tableRange.CharacterFormat.FontName = "Microsoft YaHei";
      }
    }

    // Apply table style
    table.ApplyStyle({ builtinTableStyle: docModule.DefaultTableStyle.TableColorful2 });

    // Define output file name and save
    const outputFileName = "InsertTableIntoTextBox_output.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.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>Insert Table into a Word Textbox</h1>
      <button onClick={InsertTableIntoTextBox}>
        Generate
      </button>
    </div>
  );
}

export default App;

Table created inside the textbox, contained within the textbox and freely positionable

Table created inside the textbox, contained within the textbox and freely positionable


FAQ

Image in the textbox is truncated or not fully displayed

Cause: The textbox size does not match the image aspect ratio, so the dimensions specified when creating the textbox cannot fully accommodate the image.

Solution: Adjust the textbox size to match the image proportions, or choose an image that fits the textbox dimensions:

let tb = paragraph.AppendTextBox(400, 300);

Table in the textbox has no visible borders

Cause: The showBorder parameter was not set or was set to false when creating the table, resulting in a borderless table.

Solution: Confirm the showBorder parameter is set to true when creating the table:

let table = textbox.Body.AddTable({ showBorder: true });

Textbox position is not as expected after saving

Cause: The HorizontalOrigin or VerticalOrigin values were configured incorrectly, causing the positioning reference point to differ from what was expected.

Solution: Choose the appropriate origin type based on your requirements, and fine-tune the position using HorizontalPosition / VerticalPosition:

tb.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
tb.Format.HorizontalPosition = 50;
tb.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
tb.Format.VerticalPosition = 50;

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.

Splitting Excel files into separate files by worksheet, by row, or by column is a common requirement for data distribution and management. Spire.XLS for JavaScript performs the splitting process 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.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module is initialized.


Split by Worksheet

Splitting by worksheet exports each sheet in a multi-sheet workbook as an independent Excel file. When a workbook contains multiple worksheets, each representing different data such as separate departments or months, you can split each worksheet into its own file. Spire.XLS accomplishes this by iterating through all worksheets in the source file, creating new workbooks, and copying each sheet. The steps are as follows:

  1. Create a Workbook object and load the source Excel document with LoadFromFile().
  2. Iterate through all worksheets in the source document.
  3. Create a new Workbook object.
  4. Copy the source worksheet to the default worksheet of the new workbook using the CopyFrom method.
  5. Get the worksheet name via sheet.Name as the output file name.
  6. Save the new workbook as an Excel file with SaveToFile().

Below is a complete code example demonstrating how to split worksheets into separate Excel files:

function App() {
  const splitByWorksheet = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check if the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load fonts and the Excel file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Sample.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // Iterate through each worksheet and export it as a separate file
    for (let i = 0; i < workbook.Worksheets.Count; i++) {
      let sheet = workbook.Worksheets.get(i);

      // Create a new workbook and copy the current worksheet
      let newWorkbook = new xlsModule.Workbook();
      let newSheet = newWorkbook.Worksheets.get(0);
      newSheet.CopyFrom(sheet);

      // Use the worksheet name as the output file name
      const outputFileName = `${sheet.Name}.xlsx`;
      newWorkbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
      newWorkbook.Dispose();

      // Read the split file from VFS and trigger a browser download
      const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
      const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = outputFileName;
      a.click();
      URL.revokeObjectURL(url);
    }

    // Release resources
    workbook.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Excel By Worksheet</h1>
      <button onClick={splitByWorksheet}>
        Generate
      </button>
    </div>
  );
}

export default App;

After splitting by worksheet, each resulting file contains a single worksheet from the original workbook

After splitting by worksheet, each resulting file contains a single worksheet from the original workbook


Split by Row

Splitting by row is suitable for breaking up large tables into multiple smaller files by a fixed number of rows, making pagination and distribution easier. When a worksheet contains a large amount of data rows that need to be split into multiple files, Spire.XLS accomplishes this by copying source rows one by one into a new workbook. The steps are as follows:

  1. Create a Workbook object, load the source Excel document with LoadFromFile(), and retrieve the first worksheet.
  2. Create a new Workbook object.
  3. Use a loop to call the Copy method row by row, copying specified rows from the source worksheet to the new worksheet.
  4. Copy the column widths from the source worksheet to the new worksheet.
  5. Save the new workbook as an Excel file with SaveToFile().
  6. Repeat the steps above to create more split files, copying the header row separately when needed.

Below is a complete code example demonstrating how to split a worksheet into multiple Excel files by row:

function App() {
  const splitByRow = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check if the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load fonts and the Excel file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Sample.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // Create a new workbook (comes with one default worksheet)
    let newWorkbook1 = new xlsModule.Workbook();
    let newSheet1 = newWorkbook1.Worksheets.get(0);

    // Copy rows 1-5 to the target file
    let destRow = 1;
    for (let i = 0; i < 5; i++) {
      sheet.Copy({
        sourceRange: sheet.Rows[i],
        worksheet: newSheet1,
        destRow: destRow,
        destColumn: 1,
        copyStyle: true
      });
      destRow++;
    }

    // Copy column widths
    for (let c = 0; c < sheet.Columns.length; c++) {
      newSheet1.SetColumnWidth(c + 1, sheet.GetColumnWidth(c + 1));
    }

    // Save the first split file
    const outputFileName1 = "Rows1-5.xlsx";
    newWorkbook1.SaveToFile({ fileName: outputFileName1, version: xlsModule.ExcelVersion.Version2010 });
    newWorkbook1.Dispose();

    // Read file data from VFS
    const fileData1 = window.dotnetRuntime.Module.FS.readFile(outputFileName1);

    // Create a second new workbook
    let newWorkbook2 = new xlsModule.Workbook();
    let newSheet2 = newWorkbook2.Worksheets.get(0);

    destRow = 1;

    // Copy the header row
    sheet.Copy({
      sourceRange: sheet.Rows[0],
      worksheet: newSheet2,
      destRow: destRow,
      destColumn: 1,
      copyStyle: true
    });
    destRow++;

    // Copy rows 6-10 to the second target file
    for (let i = 5; i < 10; i++) {
      sheet.Copy({
        sourceRange: sheet.Rows[i],
        worksheet: newSheet2,
        destRow: destRow,
        destColumn: 1,
        copyStyle: true
      });
      destRow++;
    }

    // Copy column widths
    for (let c = 0; c < sheet.Columns.length; c++) {
      newSheet2.SetColumnWidth(c + 1, sheet.GetColumnWidth(c + 1));
    }

    // Save the second split file
    const outputFileName2 = "Rows6-10.xlsx";
    newWorkbook2.SaveToFile({ fileName: outputFileName2, version: xlsModule.ExcelVersion.Version2010 });
    newWorkbook2.Dispose();

    // Read file data from VFS
    const fileData2 = window.dotnetRuntime.Module.FS.readFile(outputFileName2);

    // Package the split files into a ZIP for download
    const zip = new JSZip();
    zip.file(outputFileName1, fileData1);
    zip.file(outputFileName2, fileData2);
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const zipUrl = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = zipUrl;
    a.download = "SplitByRows.zip";
    a.click();
    URL.revokeObjectURL(zipUrl);

    // Release resources
    workbook.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Excel By Row</h1>
      <button onClick={splitByRow}>
        Generate
      </button>
    </div>
  );
}

export default App;

After splitting by row, each file contains the header row and the specified number of data rows

After splitting by row, each file contains the header row and the specified number of data rows


Split by Column

Splitting by column is suitable for breaking up wide tables into multiple files by column groups, making the data structure clearer. When a worksheet contains many columns and you need to split different column groups into separate files, Spire.XLS accomplishes this by copying source columns one by one into a new workbook. The steps are as follows:

  1. Create a Workbook object, load the source Excel document with LoadFromFile(), and retrieve the first worksheet.
  2. Create a new Workbook object.
  3. Use a loop to call the Copy method column by column, copying specified columns from the source worksheet to the new worksheet.
  4. Copy the column widths from the source worksheet to the new worksheet.
  5. Save the new workbook as an Excel file with SaveToFile().
  6. Repeat the steps above to create more split files.

Below is a complete code example demonstrating how to split a worksheet into multiple Excel files by column:

function App() {
  const splitByColumn = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check if the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load fonts and the Excel file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Sample.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // Create a new workbook and copy columns 1-2 (columns A-B) to the new file
    let newWorkbook1 = new xlsModule.Workbook();
    let newSheet1 = newWorkbook1.Worksheets.get(0);

    for (let i = 1; i <= 2; i++) {
      sheet.Copy({
        sourceRange: sheet.Columns[i - 1],
        worksheet: newSheet1,
        destRow: 1,
        destColumn: i,
        copyStyle: true
      });
    }

    // Copy column widths
    for (let i = 1; i <= 2; i++) {
      newSheet1.SetColumnWidth(i, sheet.GetColumnWidth(i));
    }

    // Save the first split file
    const outputFileName1 = "ColumnsAB.xlsx";
    newWorkbook1.SaveToFile({ fileName: outputFileName1, version: xlsModule.ExcelVersion.Version2010 });
    newWorkbook1.Dispose();

    // Read file data from VFS
    const fileData1 = window.dotnetRuntime.Module.FS.readFile(outputFileName1);

    // Create a second new workbook and copy columns 3-4 (columns C-D) to the new file
    let newWorkbook2 = new xlsModule.Workbook();
    let newSheet2 = newWorkbook2.Worksheets.get(0);

    for (let i = 3; i <= 4; i++) {
      sheet.Copy({
        sourceRange: sheet.Columns[i - 1],
        worksheet: newSheet2,
        destRow: 1,
        destColumn: i - 2,
        copyStyle: true
      });
    }

    // Copy column widths
    for (let i = 3; i <= 4; i++) {
      newSheet2.SetColumnWidth(i - 2, sheet.GetColumnWidth(i));
    }

    // Save the second split file
    const outputFileName2 = "ColumnsCD.xlsx";
    newWorkbook2.SaveToFile({ fileName: outputFileName2, version: xlsModule.ExcelVersion.Version2010 });
    newWorkbook2.Dispose();

    // Read file data from VFS
    const fileData2 = window.dotnetRuntime.Module.FS.readFile(outputFileName2);

    // Package the split files into a ZIP for download
    const zip = new JSZip();
    zip.file(outputFileName1, fileData1);
    zip.file(outputFileName2, fileData2);
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const zipUrl = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = zipUrl;
    a.download = "SplitByColumns.zip";
    a.click();
    URL.revokeObjectURL(zipUrl);

    // Release resources
    workbook.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Excel By Column</h1>
      <button onClick={splitByColumn}>
        Generate
      </button>
    </div>
  );
}

export default App;

After splitting by column, each file contains a portion of the columns from the original worksheet

After splitting by column, each file contains a portion of the columns from the original worksheet


FAQ

Worksheet name shows as default (Sheet1) instead of the original name

Cause: The CopyFrom method only copies worksheet content — it does not retain the original worksheet name. The new workbook's default worksheet keeps its default name.

Solution: Manually set the worksheet name after copying using newSheet.Name = sheet.Name:

let newSheet = newWorkbook.Worksheets.get(0);
newSheet.CopyFrom(sheet);
newSheet.Name = sheet.Name;

VFS file loading fails or path is incorrect

Cause: The file path or VFS file name is incorrect, or the required font files have not been loaded into VFS, causing the workbook to fail to load.

Solution: Verify that the FetchFileToVFS parameters use the correct paths. The font file path should be /Library/Fonts/, and ensure the font file name matches exactly (e.g., arial.ttf):

await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', fontSourcePath);

Get a Free License

Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

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.

Page 1 of 6
page 1