Add a Table of Contents to a New Word Document with JavaScript in React

Creating a table of contents for a long document lets readers locate chapters quickly, and makes it easy to re-sync entries and page numbers after the document structure changes. Using a "Spire.Doc Developer Guide" as an example, this article shows how to build a Word document with multi-level headings from scratch and add a table of contents to it. Spire.Doc for JavaScript builds and edits Word documents directly in the browser via WebAssembly, managing font resources through a virtual file system (VFS) — no backend server required.

This article covers two core features:

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


Add a Default Table of Contents

In Word, a table of contents is essentially a TOC field whose entries come from the paragraphs in the document that have a heading style applied. Creating a default table of contents has three phases: first, load the font file into the WASM virtual file system via FetchFileToVFS; then instantiate a Document and build the content with AddSection and AddParagraph, calling ApplyStyle on the paragraphs that should appear in the table of contents to apply heading styles, and inserting the TOC field at the beginning of the document with AppendTOC; finally, call UpdateTableOfContents to fill in the entries and page numbers, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

The sample document contains three chapters and twelve multi-level headings in total, spanning Heading 1 through Heading 3, which makes it easy to observe how the table of contents collects multi-level headings.

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the font into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a document instance and add a section
    const doc = new docModule.Document();
    let section = doc.AddSection();

    // Insert a TOC field at the beginning of the document, collecting Heading 1 through Heading 3 entries
    let tocPara = section.AddParagraph();
    tocPara.AppendTOC(1, 3);

    // Add the document title
    let characterFormat = new docModule.CharacterFormat(doc);
    characterFormat.FontName ="Arial";
    let title = section.AddParagraph();
    let titleRun = title.AppendText("Spire.Doc Developer Guide");
    titleRun.ApplyCharacterFormat(characterFormat);
    titleRun.CharacterFormat.FontSize = 24;
    title.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;

    // Chapter 1 Overview (Heading 1)
    let p = section.AddParagraph();
    p.AppendText("Chapter 1 Overview").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("Spire.Doc for JavaScript lets developers create, edit, and save Word documents directly in the browser, with no backend service involved at any point.").ApplyCharacterFormat(characterFormat);;

    // 1.1 What Is Spire.Doc for JavaScript (Heading 2)
    p = section.AddParagraph();
    p.AppendText("1.1 What Is Spire.Doc for JavaScript").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It is a Word document processing library built on WebAssembly that manages fonts and document files through a virtual file system (VFS) and exposes an API shaped the same as the .NET version.").ApplyCharacterFormat(characterFormat);;

    // 1.2 Use Cases (Heading 2)
    p = section.AddParagraph();
    p.AppendText("1.2 Use Cases").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It suits scenarios that require document processing on the client side, such as online contract signing, batch report generation, and resume template filling.").ApplyCharacterFormat(characterFormat);;

    // Chapter 2 Core Capabilities (Heading 1)
    p = section.AddParagraph();
    p.AppendText("Chapter 2 Core Capabilities").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("Spire.Doc for JavaScript covers the entire document processing chain, from content construction and layout adjustment to format export, all of which can be completed in the browser.").ApplyCharacterFormat(characterFormat);;

    // 2.1 Document Processing (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.1 Document Processing").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It supports creating and modifying common document elements such as paragraphs, styles, tables, images, headers, and footers, while preserving the original layout information.").ApplyCharacterFormat(characterFormat);;

    // 2.1.1 Paragraphs and Styles (Heading 3)
    p = section.AddParagraph();
    p.AppendText("2.1.1 Paragraphs and Styles").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
    section.AddParagraph().AppendText("Add a paragraph with AddParagraph and apply a built-in style with ApplyStyle to quickly build a clearly structured document skeleton.").ApplyCharacterFormat(characterFormat);;

    // 2.1.2 Tables and Images (Heading 3)
    p = section.AddParagraph();
    p.AppendText("2.1.2 Tables and Images").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
    section.AddParagraph().AppendText("Tables and images can be written directly into a specified paragraph or nested inside a textbox, meeting the layout needs of complex documents.").ApplyCharacterFormat(characterFormat);;

    // 2.2 Format Conversion (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.2 Format Conversion").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("SaveToFile converts documents to PDF, HTML, Markdown, and other formats, with the whole conversion completed in the browser.").ApplyCharacterFormat(characterFormat);;

    // 2.3 Batch Processing (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.3 Batch Processing").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("Combined with the runtime efficiency of WebAssembly, multiple documents can be loaded at once and processed in sequence, avoiding frequent file uploads and downloads.").ApplyCharacterFormat(characterFormat);;

    // Chapter 3 Getting Started (Heading 1)
    p = section.AddParagraph();
    p.AppendText("Chapter 3 Getting Started").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("This chapter covers the preparation needed to integrate Spire.Doc for JavaScript into a React project and produce your first document.").ApplyCharacterFormat(characterFormat);;

    // 3.1 Environment Setup (Heading 2)
    p = section.AddParagraph();
    p.AppendText("3.1 Environment Setup").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("Install Spire.Doc for JavaScript in your React project and place the font files and WASM resources in the public directory to get started.").ApplyCharacterFormat(characterFormat);;

    // 3.2 The First Example (Heading 2)
    p = section.AddParagraph();
    p.AppendText("3.2 The First Example").ApplyCharacterFormat(characterFormat);;
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("After initializing the module, create a Document instance, add content, and save it; then read the resulting file from VFS to trigger a browser download.").ApplyCharacterFormat(characterFormat);;

    // Update the table of contents to fill in entries and page numbers
    doc.UpdateTableOfContents();

    // Define the output file name and save
    const outputFileName = "Create a Default TOC in Word Document.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Click the button below to create a default table of contents in a Word document</h1>
      <button onClick={AddTableOfContentsToNewDocument}>
        Generate
      </button>
    </div>
  );
}

export default App;

After a TOC field is inserted with AppendTOC and updated, the beginning of the document holds a default table of contents that collects three heading levels, complete with page numbers and hyperlinks.

Default table of contents added to a new document via AppendTOC


Add a Custom Table of Contents

The table of contents generated by AppendTOC uses Word's default field switches. When you need to control its exact behavior, you can construct a TableOfContent object directly and specify the switch string instead. The difference from the previous feature lies in how it is inserted: you must manually add the table of contents object to a paragraph, supply the field separator and field end marks, and assign the object to document.TOC. The commonly used field switches and their meanings are as follows:

Switch Description
\o "1-3" Collects entries by built-in heading styles; here it means including Heading 1 through Heading 3
\h Turns table of contents entries into hyperlinks that jump to the corresponding chapter when clicked
\z Hides page numbers and tab leaders in Web Layout view
\u Collects entries by the outline level of the paragraphs

For example, changing the switch string to \o "1-2" means only Heading 1 and Heading 2 entries are collected, and Heading 3 no longer appears in the table of contents; removing \h means the entries no longer support jumping.

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the font into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a document instance and add a section
    const doc = new docModule.Document();
    let section = doc.AddSection();

    // Construct a table of contents object with custom field switches
    let toc = new docModule.TableOfContent(doc, "{\\o \"1-2\" \\h \\z \\u}");

    // Add the table of contents object to a paragraph
    let tocPara = section.AddParagraph();
    tocPara.Items.Add(toc);

    // Supply the field separator and field end marks
    tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
    tocPara.AppendText("TOC");
    tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);

    // Bind this table of contents to the document
    doc.TOC = toc;

    // Add the document title
    let characterFormat = new docModule.CharacterFormat(doc);
    characterFormat.FontName ="Arial";
    let title = section.AddParagraph();
    let titleRun = title.AppendText("Spire.Doc Developer Guide");
    titleRun.ApplyCharacterFormat(characterFormat);
    titleRun.CharacterFormat.FontSize = 24;
    title.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;

    // Chapter 1 Overview (Heading 1)
    let p = section.AddParagraph();
    p.AppendText("Chapter 1 Overview").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("Spire.Doc for JavaScript lets developers create, edit, and save Word documents directly in the browser, with no backend service involved at any point.").ApplyCharacterFormat(characterFormat);
    
    // 1.1 What Is Spire.Doc for JavaScript (Heading 2)
    p = section.AddParagraph();
    p.AppendText("1.1 What Is Spire.Doc for JavaScript").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It is a Word document processing library built on WebAssembly that manages fonts and document files through a virtual file system (VFS) and exposes an API shaped the same as the .NET version.").ApplyCharacterFormat(characterFormat);

    // 1.2 Use Cases (Heading 2)
    p = section.AddParagraph();
    p.AppendText("1.2 Use Cases").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It suits scenarios that require document processing on the client side, such as online contract signing, batch report generation, and resume template filling.").ApplyCharacterFormat(characterFormat);

    // Chapter 2 Core Capabilities (Heading 1)
    p = section.AddParagraph();
    p.AppendText("Chapter 2 Core Capabilities").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("Spire.Doc for JavaScript covers the entire document processing chain, from content construction and layout adjustment to format export, all of which can be completed in the browser.").ApplyCharacterFormat(characterFormat);

    // 2.1 Document Processing (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.1 Document Processing").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("It supports creating and modifying common document elements such as paragraphs, styles, tables, images, headers, and footers, while preserving the original layout information.").ApplyCharacterFormat(characterFormat);

    // 2.1.1 Paragraphs and Styles (Heading 3)
    p = section.AddParagraph();
    p.AppendText("2.1.1 Paragraphs and Styles").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
    section.AddParagraph().AppendText("Add a paragraph with AddParagraph and apply a built-in style with ApplyStyle to quickly build a clearly structured document skeleton.").ApplyCharacterFormat(characterFormat);

    // 2.1.2 Tables and Images (Heading 3)
    p = section.AddParagraph();
    p.AppendText("2.1.2 Tables and Images").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading3 });
    section.AddParagraph().AppendText("Tables and images can be written directly into a specified paragraph or nested inside a textbox, meeting the layout needs of complex documents.").ApplyCharacterFormat(characterFormat);

    // 2.2 Format Conversion (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.2 Format Conversion").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("SaveToFile converts documents to PDF, HTML, Markdown, and other formats, with the whole conversion completed in the browser.").ApplyCharacterFormat(characterFormat);

    // 2.3 Batch Processing (Heading 2)
    p = section.AddParagraph();
    p.AppendText("2.3 Batch Processing").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("Combined with the runtime efficiency of WebAssembly, multiple documents can be loaded at once and processed in sequence, avoiding frequent file uploads and downloads.").ApplyCharacterFormat(characterFormat);

    // Chapter 3 Getting Started (Heading 1)
    p = section.AddParagraph();
    p.AppendText("Chapter 3 Getting Started").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
    section.AddParagraph().AppendText("This chapter covers the preparation needed to integrate Spire.Doc for JavaScript into a React project and produce your first document.").ApplyCharacterFormat(characterFormat);

    // 3.1 Environment Setup (Heading 2)
    p = section.AddParagraph();
    p.AppendText("3.1 Environment Setup").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("Install Spire.Doc for JavaScript in your React project and place the font files and WASM resources in the public directory to get started.").ApplyCharacterFormat(characterFormat);

    // 3.2 The First Example (Heading 2)
    p = section.AddParagraph();
    p.AppendText("3.2 The First Example").ApplyCharacterFormat(characterFormat);
    p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
    section.AddParagraph().AppendText("After initializing the module, create a Document instance, add content, and save it; then read the resulting file from VFS to trigger a browser download.").ApplyCharacterFormat(characterFormat);

    // Update the table of contents to fill in entries and page numbers
    doc.UpdateTableOfContents();

    // Define the output file name and save
    const outputFileName = "Create a Custom TOC in Word Document.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Click the button below to create a custom table of contents in a Word document</h1>
      <button onClick={CustomizeTableOfContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

The table of contents generated with a TableOfContent object and custom field switches has its entry levels, hyperlinks, and page numbers all determined by the switch string.

Table of contents added to a new document via custom field switches


FAQ

The generated table of contents is empty

Cause: A TOC field collects entries by heading style. If the paragraphs do not have built-in heading styles such as Heading1 to Heading3 applied, no entries will appear in the table of contents after updating, even if the TOC field was inserted successfully.

Solution: Call ApplyStyle on the paragraphs that should appear in the table of contents to apply a heading style:

p.AppendText("Chapter 1 Overview");
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });

The table of contents contains fewer heading levels than expected

Cause: The two parameters of AppendTOC correspond to the starting and ending heading levels collected by the table of contents. If you pass AppendTOC(1, 2), Heading 3 will not appear in the table of contents. With custom switches, \o "1-2" produces the same result.

Solution: Adjust the parameter range to the levels you need to collect — for example, to include Heading 1 through Heading 3:

tocPara.AppendTOC(1, 3);

Page numbers in the table of contents are missing or incorrect

Cause: AppendTOC only inserts the TOC field itself; the field content must be updated explicitly. If UpdateTableOfContents is not called before saving, the generated table of contents contains only the field code, with no entries or page numbers.

Solution: Call the update method before SaveToFile:

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

Get a Free License

If you wish to remove the evaluation message from the resulting document, or to eliminate functional limitations, please contact our sales team to request a 30-day temporary license.