JavaScript (260)
Numbers stored in Excel cells often need to be displayed in different formats, such as keeping two decimal places, adding thousands separators, displaying amounts as currency, highlighting negative numbers in red, or showing them as percentages or in scientific notation. Proper number formatting not only makes reports easier to read but also avoids errors caused by manual entry and layout. Spire.XLS for JavaScript completes this directly in the browser based on WebAssembly, and manages input/output files through a virtual file system (VFS), with no backend service required.
Spire.XLS for JavaScript sets number formats through the NumberFormat property of the cell range Range. NumberFormat accepts Excel-standard format code strings. The table below lists the commonly used format code symbols and their meanings:
| Symbol | Description |
|---|---|
0 |
Digit placeholder that always displays a digit, padding with 0 when there are not enough digits |
# |
Digit placeholder that shows only significant digits; extra zeros are not displayed |
? |
Digit placeholder that leaves space for insignificant zeros, used to align decimal points |
. |
Decimal point |
, |
Thousands separator |
% |
Multiplies the number by 100 and displays it as a percentage |
E+ / E- |
Exponent part of scientific notation |
; |
Separates the formats for positive numbers, negative numbers, zeros and text |
[Red] and other color codes |
Sets the display color of the number, e.g. [Red], [Blue], [Green] |
[DBNum1] / [DBNum2] |
Converts numbers to Chinese lowercase / uppercase numerals |
@ |
Text placeholder that displays the number as text |
This article covers three core features:
- Set Decimal Places and Thousands Separator
- Set Currency Format and Highlight Negative Values
- Set Percentage, Scientific Notation and Other Formats
For installation and project configuration, 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.
Set Decimal Places and Thousands Separator
When processing amounts, statistics and other data, it is often necessary to keep numbers to a specified number of decimal places, or to add thousands separators for readability. Presenting the same values with the right format codes makes reports easier to scan and avoids manual padding or misplaced commas.
The steps are:
- Load the font file into the VFS for text measurement.
- Create a
Workbookobject and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
Range.Textproperty to write the title, column headers, and the format codes used as description text. - Use the
Range.NumberValueproperty to write the values, and set the corresponding format codes with theRange.NumberFormatproperty. - Use the
Workbook.SaveToFile()method to save the workbook, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to set number formats with different decimal places and thousands separators for cells in React:
function App() {
const setDecimalPlacesAndThousands = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font into the VFS for text measurement
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Create a new workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
// Write the title and column headers
sheet.Range.get("B1").Text = "Number Formatting Sample";
sheet.Range.get("B3").Text = "Format Code";
sheet.Range.get("C3").Text = "Result";
// Write the format code in column B, write the value in column C and apply the corresponding number format
sheet.Range.get("B4").Text = "0";
sheet.Range.get("C4").NumberValue = 1234.5678;
sheet.Range.get("C4").NumberFormat = "0";
sheet.Range.get("B5").Text = "0.00";
sheet.Range.get("C5").NumberValue = 1234.5678;
sheet.Range.get("C5").NumberFormat = "0.00";
sheet.Range.get("B6").Text = "#,##0.00";
sheet.Range.get("C6").NumberValue = 1234.5678;
sheet.Range.get("C6").NumberFormat = "#,##0.00";
// Add a background color to the format-code cells and set the column widths
sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
sheet.SetColumnWidth(2, 26);
sheet.SetColumnWidth(3, 20);
// Save the document
const outputFileName = 'NumberFormat_Decimals_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Decimal Places and Thousands Separator</h1>
<button onClick={setDecimalPlacesAndThousands}>
Start
</button>
</div>
);
}
export default App;
The worksheet after setting decimal places and thousands separator:

Set Currency Format and Highlight Negative Values
In financial reports, amounts are usually displayed in currency format, while negative numbers need to be highlighted so they can be spotted quickly. An Excel number format code can consist of several parts separated by semicolons, so positive and negative values in the same column can be displayed differently, and negative values can be colored on their own.
The steps are:
- Load the font file into the VFS for text measurement.
- Create a
Workbookobject and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
Range.Textproperty to write the title, column headers, and the currency format codes used as description text. - Use the
Range.NumberValueproperty to write the values (including negative numbers), and set the format codes that contain a negative part with theRange.NumberFormatproperty. - Use the
Workbook.SaveToFile()method to save the workbook, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to set a currency format for amounts in React and display negative numbers in red:
function App() {
const setCurrencyFormat = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font into the VFS for text measurement
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Create a new workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
// Write the title and column headers
sheet.Range.get("B1").Text = "Number Formatting Sample";
sheet.Range.get("B3").Text = "Format Code";
sheet.Range.get("C3").Text = "Result";
// Set the currency format: two decimal places with a thousands separator
sheet.Range.get("B4").Text = "$#,##0.00";
sheet.Range.get("C4").NumberValue = 1234.5678;
sheet.Range.get("C4").NumberFormat = "$#,##0.00";
// Negative numbers with a thousands separator displayed in red
sheet.Range.get("B5").Text = "#,##0.00;[Red]-#,##0.00";
sheet.Range.get("C5").NumberValue = -1234.5678;
sheet.Range.get("C5").NumberFormat = "#,##0.00;[Red]-#,##0.00";
// Negative numbers with two decimal places displayed in red
sheet.Range.get("B6").Text = "0.00;[Red]-0.00";
sheet.Range.get("C6").NumberValue = -1234.5678;
sheet.Range.get("C6").NumberFormat = "0.00;[Red]-0.00";
// Add a background color to the format-code cells and set the column widths
sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
sheet.SetColumnWidth(2, 26);
sheet.SetColumnWidth(3, 20);
// Save the document
const outputFileName = 'NumberFormat_Currency_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Currency Format and Highlight Negative Values</h1>
<button onClick={setCurrencyFormat}>
Start
</button>
</div>
);
}
export default App;
The worksheet after setting the currency format and highlighting negative values:

Set Percentage, Scientific Notation and Other Formats
Besides the common decimal, thousands separator and currency formats, reports often call for percentages, scientific notation and Chinese uppercase amounts. Percentages make proportions easier to compare, scientific notation suits very large or very small numbers, and Chinese uppercase is used on formal financial documents.
The steps are:
- Load the font file into the VFS for text measurement.
- Create a
Workbookobject and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
Range.Textproperty to write the title, column headers, and the format codes used as description text. - Use the
Range.NumberValueproperty to write the values, and set the percentage, scientific notation or Chinese uppercase format codes respectively with theRange.NumberFormatproperty. - Use the
Workbook.SaveToFile()method to save the workbook, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to set percentage, scientific notation and Chinese uppercase formats for cells in React:
function App() {
const setSpecialNumberFormat = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font into the VFS for text measurement
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Create a new workbook and get the first worksheet
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
// Write the title and column headers
sheet.Range.get("B1").Text = "Number Formatting Sample";
sheet.Range.get("B3").Text = "Format Code";
sheet.Range.get("C3").Text = "Result";
// Percentage format: the stored value is multiplied by 100 and a percent sign is shown
sheet.Range.get("B4").Text = "0.00%";
sheet.Range.get("C4").NumberValue = 0.1234;
sheet.Range.get("C4").NumberFormat = "0.00%";
// Scientific notation format
sheet.Range.get("B5").Text = "0.00E+00";
sheet.Range.get("C5").NumberValue = 123456789;
sheet.Range.get("C5").NumberFormat = "0.00E+00";
// Chinese uppercase format (uppercase for RMB amounts)
sheet.Range.get("B6").Text = "[DBNum2][$-804]General";
sheet.Range.get("C6").NumberValue = 1234567;
sheet.Range.get("C6").NumberFormat = "[DBNum2][$-804]General";
// Add a background color to the format-code cells and set the column widths
sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
sheet.SetColumnWidth(2, 26);
sheet.SetColumnWidth(3, 20);
// Save the document
const outputFileName = 'NumberFormat_Special_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Percent, Scientific Notation and Other Formats</h1>
<button onClick={setSpecialNumberFormat}>
Start
</button>
</div>
);
}
export default App;
The worksheet after setting percentage, scientific notation and other special formats:

FAQ
The displayed value does not change after setting the number format
Cause: The value written to the cell is actually stored as text, or the data itself is not numeric, so NumberFormat cannot take effect on a number.
Solution: Make sure the value is written with the Range.NumberValue property (rather than a Text or Value string) before setting the NumberFormat, for example:
sheet.Range.get("C4").NumberValue = 1234.5678;
sheet.Range.get("C4").NumberFormat = "0.00";
The value is enlarged after using the percentage format
Cause: Excel displays a percentage format as "stored value × 100". For example, the stored value 12.34 combined with the 0.00% format is displayed as 1234.00%, not 12.34%.
Solution: To display 12.34%, the stored value should be 0.1234. If the original data was entered as a percentage number, divide it by 100 before setting the format.
Negative numbers are not displayed in red
Cause: When a format code contains only one section, positive numbers, negative numbers and zeros all use that section, so [Red] in it affects every value. The color applies only to negative numbers when it is placed in the negative section.
Solution: Split the positive and negative parts with a semicolon and put [Red] in the negative part, for example 0.00;[Red]-0.00.
Obtain 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.
Word documents have long carried manuals, reports and contracts, while Markdown serves documentation sites, knowledge bases and code repositories as plain text. Moving existing Word content into Markdown is a routine step in documentation migration and content publishing.
If the conversion runs on a server, the file has to be uploaded first, and the original content leaves the user's device. This article performs the conversion in the browser with Spire.Doc for JavaScript: the Word document is read through a virtual file system (VFS), the export result is written back to the VFS, and the page then reads it and triggers the download. Everything runs in the browser as a WebAssembly module, so no Office installation and no backend service are required.
This article covers three features:
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume that Spire.Doc is installed and the WebAssembly module is initialized.
Convert a Word Document to Markdown
The conversion takes two steps: Document.LoadFromFile() reads the .docx into a document object, and Document.SaveToFile() writes a .md file with FileFormat.Markdown as the target format. The saved result first lands in the VFS and is then read and downloaded by the page.
The document structure is written out with the corresponding syntax; the main mappings are as follows:
| Word content | Markdown output |
|---|---|
| Paragraphs with the built-in Heading 1, Heading 2 and other heading styles applied | # and ## headings of the matching level |
| Bold, italic | **text**, *text* |
| Bulleted list | - item |
| Numbered list | 1. item |
| Table | Pipe table with a bold header |
| Image | , with the image written out separately |
| Hyperlink | [text](url) |
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the sample document
Product Release Notes.docxinto the virtual file system (VFS). - Create a
Documentobject and load the document. - Set
MarkdownExportOptions.ImagesFoldertoimagesso that the images are written into that folder. - Save the document with
SaveToFile()asFileFormat.Markdown, producingProduct Release Notes.md. - Read the Markdown text and the image files from the VFS and trigger their browser downloads.
The code for converting a Word document to Markdown is as follows:
// Read the file from VFS and trigger the download
function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
function App() {
const convertToMarkdown = 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 sample document into VFS
await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the Word document
const doc = new docModule.Document();
doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);
// Write the images into the images folder in VFS and reference them by relative path
doc.MarkdownExportOptions.ImagesFolder = 'images';
// Save as Markdown; the result is written into VFS
const outputFileName = 'Product Release Notes.md';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
doc.Dispose();
const FS = window.dotnetRuntime.Module.FS;
// Read the Markdown text and download it (UTF-8 encoding)
const text = new TextDecoder('utf-8').decode(FS.readFile(outputFileName));
downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);
// The images are stored separately, so download them as well
FS.readdir('images')
.filter((name) => name !== '.' && name !== '..')
.forEach((name) => {
const data = FS.readFile(`images/${name}`);
downloadBlob(new Blob([data], { type: 'image/png' }), name);
});
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Word to Markdown</h1>
<button onClick={convertToMarkdown}>
Convert and Download
</button>
</div>
);
}
export default App;
Once the converted Markdown is rendered in a browser, the headings, lists, table, hyperlink and image all appear in their corresponding syntax:
Markdown generated from the Word document, rendered in a browser

Save Images to a Specified Folder
When ImagesFolder is not set, images are written into the root of the VFS under a name prefixed with the output file name, for example Product Release Notes_001.png, and the Markdown references them as /Product Release Notes_001.png. That form makes the files easy to locate directly, but the reference carries a root prefix. Once ImagesFolder is set to a relative folder, the images are written into that folder in the VFS and the references become relative paths:
doc.MarkdownExportOptions.ImagesFolder = 'images';
The conversion result then contains references such as images/Product Release Notes_001.png. The images have to be downloaded together with the .md file; the page can list the folder content with FS.readdir() and read and download each file, as shown in the example above. If the result should be a single file, use the Base64 approach described in the next section instead.
Embed Images as Base64
With MarkdownExportOptions.ImagesAsBase64 = true, the images in the document are no longer written out separately; they are encoded as data URIs and placed directly inside the brackets of ![](). The result is a single .md file, so image paths never break when the file is copied, uploaded or committed to a repository.
The trade-off is file size: Base64-encoded data is about 4/3 the size of the original image. With many images or high-resolution images, the size of a single Markdown file grows noticeably.
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the sample document
Product Release Notes.docxinto the virtual file system (VFS). - Create a
Documentobject and load the document. - Set
MarkdownExportOptions.ImagesAsBase64totrue. - Save the document with
SaveToFile()asFileFormat.Markdown, read the text from the VFS and trigger the download.
The code for embedding images as Base64 is as follows:
function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
function App() {
const convertWithBase64 = 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 sample document into VFS
await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the Word document
const doc = new docModule.Document();
doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);
// Write the images into the Markdown as inline Base64
doc.MarkdownExportOptions.ImagesAsBase64 = true;
// Save the result file
const outputFileName = 'Product Release Notes - Inline Images.md';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
doc.Dispose();
// Read the Markdown text and download it; no separate image download is needed
const text = new TextDecoder('utf-8').decode(window.dotnetRuntime.Module.FS.readFile(outputFileName));
downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Embed Images as Base64</h1>
<button onClick={convertWithBase64}>
Convert and Download
</button>
</div>
);
}
export default App;
After embedding, the image appears in the Markdown as a long string starting with data:image/png;base64,, and no separate image file accompanies the document:
Image line in the Markdown after the image is embedded as Base64

Customize the Markdown Export Options
Document.MarkdownExportOptions controls how the Markdown is written. Beyond image handling, lists, links, tables, underlines and equations can each be adjusted separately:
| Option | Common values | Effect |
|---|---|---|
ImagesFolder |
A folder name | Images are written into the specified folder in the VFS and referenced from it |
ImagesAsBase64 |
true / false |
Images are embedded as data URIs |
ListOutputMode |
MarkdownSyntax / PlainText |
Emit list syntax, or escape the list markers as plain text |
LinkOutputMode |
Auto / Inline / Reference |
Inline links, or reference-style links |
SaveAsHtml |
None / Tables |
Keep tables in pipe syntax, or output them as HTML tables |
SaveUnderlineFormatting |
true / false |
Whether underlines are kept as ++text++ |
OfficeMathOutputMode |
Text / Image / MathML |
The output form of equations |
When SaveAsHtml is set to Tables, the export needs fonts to lay out the tables; if no fonts are loaded, saving throws Cannot find any fonts in specified font sources. The sample therefore loads the fonts into the VFS before converting. This step is not needed when only text, lists and images are exported.
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the fonts
msyh.ttcandmsyhbd.ttcand the sample document into the virtual file system (VFS). - Create a
Documentobject and load the Word document. - Get
MarkdownExportOptionsand set the image folder, list, link, table and underline options in turn. - Save the result file with
SaveToFile()asFileFormat.Markdown. - Read the Markdown text and the image files from the VFS and trigger their browser downloads.
The code for customizing the Markdown export options is as follows:
function downloadBlob(blob, fileName) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
function App() {
const convertWithOptions = 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;
}
// Exporting HTML tables needs fonts for layout, so load the fonts into VFS first
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// Load the sample document into VFS
await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);
// Create a Document instance and load the Word document
const doc = new docModule.Document();
doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);
// Customize the Markdown export options
const options = doc.MarkdownExportOptions;
options.ImagesFolder = 'images';
options.ListOutputMode = docModule.MarkdownListOutputMode.PlainText;
options.LinkOutputMode = docModule.MarkdownLinkOutputMode.Reference;
options.SaveAsHtml = docModule.MarkdownSaveAsHtml.Tables;
options.SaveUnderlineFormatting = true;
// Save the result file
const outputFileName = 'Product Release Notes - Export Options.md';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
doc.Dispose();
const FS = window.dotnetRuntime.Module.FS;
// Download the Markdown text
const text = new TextDecoder('utf-8').decode(FS.readFile(outputFileName));
downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);
// Download the written images
FS.readdir('images')
.filter((name) => name !== '.' && name !== '..')
.forEach((name) => downloadBlob(new Blob([FS.readFile(`images/${name}`)], { type: 'image/png' }), name));
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Customize the Markdown Export Options</h1>
<button onClick={convertWithOptions}>
Convert and Download
</button>
</div>
);
}
export default App;
With these options in effect, lists, links, tables and underlines are each written in a different form. Part of the output is as follows:
\- Generation time for a single document dropped from 6 minutes to under 10 seconds.
\- Contract and payroll files no longer pass through a server.
\- Exported page counts match the on-screen preview.
\1. Replace the WebAssembly resource bundle in the frontend project.
\2. Clear the browser cache and reload the page.
\3. Check the conversion results with the sample document.
The frontend calls the document interfaces through a WebAssembly module. The full interface reference is available in [the online document platform API reference] , and the sample project ships with this release.
Read the ++compatibility notes++ before upgrading and confirm that the styles of existing documents are unaffected.
[the online document platform API reference]: https://www.e-iceblue.com/
[ref1]: images/Product Release Notes - Export Options_001.png
Set the Output Style of Lists and Links
ListOutputMode defaults to MarkdownSyntax, in which bullets and numbers are written as - and 1.; with PlainText, a backslash is placed before the marker, as in \- and \1., so the text renders as plain content and no longer forms a list.
LinkOutputMode defaults to Auto, in which hyperlinks are written in the inline form [text](url); with Reference, only [text] remains in the body text, and the actual addresses are collected in the link definitions at the end of the file — images likewise switch to ![ref1] plus a definition line. When a document has many links whose addresses need to be maintained in one place, the reference style is easier to read.
const options = doc.MarkdownExportOptions;
options.ListOutputMode = docModule.MarkdownListOutputMode.PlainText;
options.LinkOutputMode = docModule.MarkdownLinkOutputMode.Reference;
Save Tables as HTML
SaveAsHtml defaults to None, in which tables are written in pipe syntax with the header text in bold; with Tables, tables are output with <table> tags, the cell styles are written inline, and column widths, borders and text formatting are preserved. The output is a single line of HTML and is no longer bound by the limits of pipe syntax.
Two points are worth noting: first, the option requires fonts to be loaded into the VFS, otherwise saving reports an error; second, if a heading follows a table directly, some Markdown parsers merge the heading line into the table — leaving one empty paragraph after the table in Word avoids this.
options.SaveAsHtml = docModule.MarkdownSaveAsHtml.Tables;
Keep Underline Formatting
Markdown has no standard syntax for underlines, so the default export drops the underlines of the Word document. With SaveUnderlineFormatting = true, underlined text is written as ++text++. That notation is an extension syntax; whether the target platform renders it as an underline depends on the parser in use.
options.SaveUnderlineFormatting = true;
FAQ
Why are the image files missing from the converted Markdown
Cause: By default, images are written out separately from the .md file, in the root of the VFS under a name prefixed with the output file name. If the page only reads and downloads the .md file, the images naturally do not appear in the download.
Solution: Set ImagesFolder to name an image folder and download the files in that folder together with the .md file; alternatively set ImagesAsBase64 = true, so that the images are written into the Markdown itself and the result is a single file.
Why has the underline in Word disappeared from the Markdown
Cause: Markdown has no standard syntax for underlines, and SaveUnderlineFormatting defaults to false, so the underline formatting is dropped during export.
Solution: Set SaveUnderlineFormatting = true and underlined text is written as ++text++. Not every Markdown parser supports that syntax, so it is worth checking how the target platform renders it before relying on it.
Why were the headings not converted to # headings
Cause: Headings are recognized by style during export; only paragraphs with a built-in heading style such as Heading 1 or Heading 2 applied become # headings of the matching level. Paragraphs that are merely bold or set to a larger font size, or that use the Title style, are still output as ordinary paragraphs.
Solution: Apply a built-in heading style to the paragraphs that should become headings; a document title that uses the Title style can be changed to Heading 1, or the # can be added manually in the Markdown after conversion.
What should I do when a heading after a table is merged into the table
Cause: When there is no blank line between a table and the heading that follows it, some Markdown parsers treat the adjacent heading line as one more row of the table.
Solution: Leave one empty paragraph after the table in Word before writing the heading, so that the exported Markdown contains a blank line; alternatively add a blank line between the table and the heading after conversion.
Does the conversion upload the file to a server
Not in the usage described in this article. Spire.Doc for JavaScript runs in the browser on WebAssembly, the Word document and the conversion result are both read and written locally through the virtual file system (VFS), and the library itself does not send files to a server. Whether a file ultimately leaves the device depends on other code added to the page.
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.
Technical documentation, API references and release notes are increasingly maintained in Markdown: it is easy to keep in a code repository, easy to review, and easy to compare between versions. For external delivery, however, the recipient usually needs a Word or PDF document. Copying and pasting into a desktop application loses the formatting, and tables and code blocks have to be rearranged by hand; converting on a server takes the source file off the user's device.
This article converts Markdown to Word documents and PDF documents in the browser with Spire.Doc for JavaScript. The library runs in the browser as a WebAssembly module, the source file is read through a virtual file system (VFS), and the conversion result is written back to the VFS before the download is triggered. The whole process requires neither an Office installation nor any backend service.
This article covers three features:
- Convert a Markdown Document to Word
- Convert a Markdown Document to PDF
- Use Template Styles to Convert Markdown to Word
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume that Spire.Doc is installed and the WebAssembly module is initialized. The sample Markdown source and the template document are placed in the public/data/ directory, and the font files in the public/static/font/ directory. To make them easy to run directly, the code for each feature is a complete single file, so the content of App.js can be replaced with it wholesale.
Convert a Markdown Document to Word
Spire.Doc for JavaScript loads the source file with Document.LoadFromFile(), and the second parameter specifies the file format. Pass FileFormat.Markdown when loading Markdown: the library parses headings, paragraphs, lists, tables, quotes, code blocks and images according to Markdown syntax rules, and turns them into the corresponding Word document objects.
The syntax used in the source file and the styles applied after conversion correspond as follows:
| Markdown syntax | Style applied after conversion | Notes |
|---|---|---|
# to ###### |
Heading 1 to Heading 6 | The six heading levels map to the six built-in heading styles respectively |
| Ordinary paragraphs | Normal | Paragraph text and inline formatting such as bold and italic are kept |
> quotes |
Quote | Converted to an indented quote paragraph |
- lists, 1. lists |
List paragraphs | Bulleted and numbered lists become automatically numbered items |
| Pipe tables | Word tables | The first row becomes the header, and cells carry thin borders |
| Backtick inline code | InlineCode | A character-level style that affects only the marked text |
| Fenced code blocks | FencedCode plus the language name | A code block marked js, for example, is given FencedCodejs |
| Indented code blocks | FencedCode | Code blocks without a language label |
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the Markdown source file into the virtual file system (VFS).
- Create a
Documentinstance and load the source file withLoadFromFile()asFileFormat.Markdown. - Save the document with
SaveToFile()as a DOCX file. - Read the result file from the VFS and trigger its browser download.
The code for converting a Markdown document to a Word document is as follows:
function App() {
const convertToWord = 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 Markdown source and the images it references into VFS
await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
// Create a Document instance and load the source file as Markdown
const doc = new docModule.Document();
doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);
// Save as a Word document
const outputFileName = 'MarkdownToWord.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2016 });
doc.Dispose();
// Read the generated file from VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Markdown to Word</h1>
<button onClick={convertToWord}>
Convert to Word
</button>
</div>
);
}
export default App;
The converted Word document is shown below; the headings, lists, quotes, tables and code blocks have all been restored according to the structure of the source file:
Word document converted with the default styles

Convert a Markdown Document to PDF
Once Markdown has been loaded, the result is a complete Word document object, so the same object can be saved as DOCX or exported directly to PDF without reading the source file again. For export, simply change the fileFormat of SaveToFile() to FileFormat.PDF. The page size and margins follow the section settings of the document, and the pagination is determined by the layout computed during export.
Unlike DOCX, PDF requires the text to be genuinely rasterized, so before export the fonts used by the document should be loaded into the font directory of the virtual file system; the examples place the fonts in /Library/Fonts/.
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the Markdown source file and the font files into the virtual file system (VFS).
- Create a
Documentinstance and load the source file withLoadFromFile()asFileFormat.Markdown. - Export the document with
SaveToFile()as a PDF file. - Read the result file from the VFS and trigger its browser download.
The code for converting a Markdown document to a PDF document is as follows:
function App() {
const convertToPdf = 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 Markdown source and the fonts into VFS
await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
// Create a Document instance and load the source file as Markdown
const doc = new docModule.Document();
doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);
// Export as a PDF document
const outputFileName = 'MarkdownToPdf.pdf';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.PDF });
doc.Dispose();
// Read the generated file from VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Markdown to PDF</h1>
<button onClick={convertToPdf}>
Convert to PDF
</button>
</div>
);
}
export default App;
The PDF document exported from the same Markdown is shown below:
PDF document exported from the same Markdown

Use Template Styles to Convert Markdown to Word
The document produced by the default rules uses the built-in styles, whose fonts, color schemes and line spacing are generic in appearance. In actual delivery, a document often has to meet the layout requirements of a company or department: which font size the headings use, which line spacing the body text uses, whether a separator line appears below a heading. These requirements have usually already been organized into a Word template; all that is needed is to copy the styles of the template onto the conversion result.
The current JavaScript version does not provide a method that copies all template styles at once, so the example handles it as follows: first find the style of the same name in the target document, then copy the character formatting and paragraph formatting of the template style onto it item by item. The style names in the template therefore have to match the names of the styles produced by the conversion; the common correspondences are as follows:
| Template style name | Affected content | Settings in the sample template |
|---|---|---|
| Normal | Body paragraphs | Microsoft YaHei 10.5 pt, grey text, 1.35 line spacing |
| Heading 1 | # level-one headings |
18 pt bold, dark blue, separator line below |
| Heading 2 | ## level-two headings |
14 pt bold, dark green |
| Heading 3 | ### level-three headings |
12 pt bold, dark blue |
| Quote | > quote paragraphs |
10 pt italic, dark green, left indent |
The specific steps are as follows:
- Get the Spire.Doc WASM module and confirm that it is ready.
- Load the Markdown source file and the template document into the virtual file system (VFS).
- Create a
Documentinstance and load the source file withLoadFromFile()asFileFormat.Markdown. - Create another
Documentinstance and load the template document, then take its styles by name. - Use the
copyStyle()helper to copy the character formatting and paragraph formatting of each template style onto the style of the same name in the target document. - Save the result file with
SaveToFile(), then read it from the VFS and trigger the download.
The code for converting Markdown with the template styles is as follows:
// Names of the styles to copy from the template; they must match the styles applied by the Markdown conversion
const TEMPLATE_STYLES = ['Normal', 'Heading 1', 'Heading 2', 'Heading 3', 'Quote'];
// Copy one style from the template document to the style of the same name in the target document
function copyStyle(src, dst) {
// Character formatting: font, font size, bold, italic, text color
const sf = src.CharacterFormat;
const df = dst.CharacterFormat;
df.FontName = sf.FontName;
df.FontSize = sf.FontSize;
df.Bold = sf.Bold;
df.Italic = sf.Italic;
df.TextColor = sf.TextColor;
// Paragraph formatting: spacing before and after, line spacing, indent, alignment and pagination control
const sp = src.ParagraphFormat;
const dp = dst.ParagraphFormat;
dp.BeforeSpacing = sp.BeforeSpacing;
dp.AfterSpacing = sp.AfterSpacing;
dp.LineSpacing = sp.LineSpacing;
dp.LineSpacingRule = sp.LineSpacingRule;
dp.LeftIndent = sp.LeftIndent;
dp.FirstLineIndent = sp.FirstLineIndent;
dp.HorizontalAlignment = sp.HorizontalAlignment;
dp.KeepFollow = sp.KeepFollow;
// Paragraph borders: the separator line below a heading
const sb = sp.Borders.Bottom;
const db = dp.Borders.Bottom;
db.BorderType = sb.BorderType;
db.LineWidth = sb.LineWidth;
db.Color = sb.Color;
db.Space = sb.Space;
}
function App() {
const convertWithTemplate = 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 Markdown source and the template document into VFS
await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('Template.docx', "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
// Load the source file as Markdown
const doc = new docModule.Document();
doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);
// Load the template document and take only its styles
const template = new docModule.Document();
template.LoadFromFile('Template.docx');
// Copy each template style onto the style of the same name in the target document
TEMPLATE_STYLES.forEach((name) => {
const src = template.Styles.FindByName(name);
const dst = doc.Styles.FindByName(name);
if (src && dst) {
copyStyle(src, dst);
}
});
template.Dispose();
// Save as a Word document
const outputFileName = 'MarkdownToWordWithTemplate.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2016 });
doc.Dispose();
// Read the generated file from VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Markdown with Template Styles</h1>
<button onClick={convertWithTemplate}>
Convert and Apply Template Styles
</button>
</div>
);
}
export default App;
The first page of the Word document after the template styles are applied is shown below; the headings, body text, quotes and tables all follow the fonts, color scheme and line spacing of the template:
First page of the Word document after applying the template styles

The Sample Markdown File
All three features share the same Sample.md, which is placed in the public/data/ directory. The sample is a product release note covering level-one to level-three headings, bulleted lists, quotes, pipe tables, fenced code blocks, inline code, images, and bold and italic text, so that the conversion result of each syntax in Word can be observed side by side.
Images are referenced by relative path; before conversion, the image files have to be loaded into the virtual file system under the same path as written in the Markdown.
The content of the sample Markdown file is as follows:
# Northstar Platform Release Notes
## Overview
Northstar 2026.4 focuses on **document processing inside the browser**. This release moves template rendering, format conversion and batch output into a single WebAssembly pipeline, so that no source file leaves the user's device.
The release has three goals:
- Reduce the time to generate one document from 6 minutes to under 10 seconds.
- Remove the server from the conversion path for contract and payroll files.
- Keep the output page count identical to the on-screen preview.
## What's New
### Browser-side conversion
All conversion work now runs in the browser through a WebAssembly module. Fonts and templates are loaded on demand through a virtual file system, and the result is written back to the same file system before download.
> Files are processed locally. The platform never stores source documents on the server.
### Supported input formats
| Format | Read | Write | Notes |
|---|---|---|---|
| Markdown | Yes | Yes | Headings, lists, tables, images |
| Word (.docx) | Yes | Yes | Styles and fields preserved |
| HTML | Yes | No | External CSS is ignored |
| PDF | Yes | Yes | Page setup follows the section |
### Markdown pipeline
The Markdown loader maps the source structure onto Word building blocks, and the result is exported from the same document object:

- Headings map onto the built-in Heading 1 to Heading 6 styles.
- Bullet and numbered lists keep their nesting level.
- Pipe tables become Word tables with a header row.
```js
const doc = new Document();
doc.LoadFromFile('Sample.md', FileFormat.Markdown);
doc.SaveToFile('Result.docx', FileFormat.Docx2016);
```
## Upgrade Notes
*Existing styles are kept* when a document is loaded, and two behaviours changed in this release:
- Markdown headings now use the built-in Heading styles instead of direct formatting.
- Table borders default to a hairline, single, light grey line.
## Timeline
| Milestone | Date | Status |
|---|---|---|
| Design review | 2026-02-12 | Done |
| Core development | 2026-03-01 | Done |
| Integration testing | 2026-07-20 | In progress |
| Acceptance | 2026-10-15 | Planned |
The template document Template.docx only provides styles; its body content does not take part in the conversion. The style names in the template have to match the names of the styles produced by the conversion (Normal, Heading 1, Heading 2, Heading 3, Quote); styles whose names do not match are not copied.
FAQ
Why is the first paragraph of the converted document a red line of English text
Cause: This line is an evaluation notice inserted by Spire.Doc for JavaScript when the file is saved. Its content is Evaluation Warning: The document was created with Spire.Doc for JavaScript., and it has nothing to do with the content of the source file.
Solution: The notice exists only in the evaluation version; once a license is applied, the saved file no longer contains this paragraph.
Why do the converted headings and body text not use the font and line spacing I expect
Cause: LoadFromFile() converts Markdown with the built-in styles — headings use Heading 1 to Heading 6 and body text uses Normal — and these styles themselves do not define the fonts and color schemes commonly used in corporate documents.
Solution: Organize the layout requirements into a Word template and copy the styles as described in Use Template Styles to Convert Markdown to Word; alternatively, after loading, modify the CharacterFormat and ParagraphFormat of the corresponding styles in doc.Styles directly.
Why do the entries after the first one in a multi-item ordered list end up merged into the first paragraph
Cause: The parser recognizes ordered lists by paragraph; when there is no blank line between items, the text of the second item onwards is treated as a continuation line of the first, and the number is kept in the paragraph as plain text.
Solution: Leave a blank line between items so that each one becomes an independently numbered list item; bulleted lists are not subject to this restriction and can be written consecutively:
1. First step.
2. Second step.
3. Third step.
Why are the images in the Markdown not displayed, or displayed exceptionally large
Cause: Images are read from the virtual file system by the path written in the Markdown; when the file has not been loaded into the VFS it cannot be embedded. Image size is converted from the original pixel dimensions at 96 dpi, so an image with a large pixel width may exceed the text width on an A4 page.
Solution: Load the image into the VFS first, under the same path as written in the Markdown:
await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);
To specify a width, write the image as an HTML img tag; the width attribute is written into the document in pixels:
<img src="pipeline.png" width="360">
What should I do when the Chinese font in the exported PDF is wrong, or appears blank or as boxes
Cause: A DOCX records only font names, which Word resolves when the file is opened; PDF requires the text to be rendered as graphics, so when the font file has not been loaded, the export can only use a substitute font or leave the text blank.
Solution: Before exporting to PDF, load the fonts used by the document into the font directory of the virtual file system; the examples place them in /Library/Fonts/. When there are many Chinese fonts, it is advisable to load both the regular and the bold face:
await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
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.
When data is split into categories and subcategories, each carrying a value, rows of a table make it hard to see at a glance how much of the whole each category represents. A treemap nests rectangles by hierarchy and sizes each rectangle by its value, so subcategories of the same category sit side by side and their proportions can be compared directly. Turning such data into a treemap, then adding labels and fill colors, produces a chart that can go straight into a report or a shared document. Spire.XLS for JavaScript creates and renders treemap charts in the browser through WebAssembly, with input and output files managed by a virtual file system (VFS) and no backend service required.
This article covers two features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume that Spire.XLS is installed and the WebAssembly module is initialized.
Create a Treemap Chart
A treemap uses rectangle area to represent values and the nesting of rectangles to represent hierarchy. To build a usable chart, start with three columns, category, item, and value, then set the chart type to tree map and fix the chart position and title. Once the chart is created, saving it in an Excel workbook lets the chart be opened, edited, or shared in spreadsheet software.
How to create a treemap chart and what it produces:
| Call | Result |
|---|---|
window.spire.FetchFileToVFS |
Loads the font file and the test data file into the VFS |
workbook.LoadFromFile({ fileName }) |
Loads the workbook that holds the two-level data |
workbook.Worksheets.get(0) |
Gets the first worksheet of the workbook |
sheet.Charts.Add() |
Adds a chart object to the worksheet |
chart.ChartType = xlsModule.ExcelChartType.TreeMap |
Sets the chart type to tree map |
chart.DataRange = sheet.Range.get("A2:C11") |
Uses two hierarchy columns and one value column as the chart data |
chart.ChartTitle |
Sets the chart title |
chart.TopRow / chart.BottomRow / chart.LeftColumn / chart.RightColumn |
Sets the position and size of the chart on the worksheet |
workbook.SaveToFile({ fileName }) |
Writes the workbook with the treemap chart into the VFS |
The steps are:
- Load the font file and the Excel test file into the VFS with
window.spire.FetchFileToVFS. - Create a workbook with
new Workbook()and load the test file withworkbook.LoadFromFile. - Get the first worksheet with
workbook.Worksheets.get(0). - Add a chart with
sheet.Charts.Add()and setchart.ChartTypetoxlsModule.ExcelChartType.TreeMap. - Set
chart.DataRangetosheet.Range.get("A2:C11"), where the first two columns are the group and the item and the third column decides the rectangle area. - Set
chart.ChartTitle, then fix the chart position and size throughchart.TopRow,chart.BottomRow,chart.LeftColumn, andchart.RightColumn. - Call
workbook.SaveToFileto writeCreateTreeMap.xlsx, release the workbook withworkbook.Dispose, and read the workbook back from the VFS to trigger the download.
Below is the complete example that creates a treemap chart and saves it in an Excel workbook in React:
function App() {
const createTreeMap = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TreeMapData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Add a chart and set its type to tree map
const chart = sheet.Charts.Add();
chart.ChartType = xlsModule.ExcelChartType.TreeMap;
// Set the data range: the first two columns are the group and the item, the third is the value
chart.DataRange = sheet.Range.get('A2:C11');
// Set the position and size of the chart
chart.TopRow = 1;
chart.BottomRow = 20;
chart.LeftColumn = 1;
chart.RightColumn = 12;
// Set the chart title
chart.ChartTitle = 'Area by cities';
// Save the workbook with the tree map chart into the VFS
const outputFileName = 'CreateTreeMap.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Create a Tree Map Chart in React</h1>
<button id="create-treemap" onClick={createTreeMap}>Create Tree Map</button>
</div>
);
}
export default App;
The workbook after creating the treemap chart:

Set Treemap Labels and Fill Colors
By default, a treemap is rendered without any data labels, so readers can rely only on the legend to tell the groups apart and cannot see which item each area stands for. Rectangles in the same group also share one color, which makes neighboring areas less distinct. Turning on name labels for the rectangles and then assigning darker fill colors in data point order places each item name directly on its area; the white labels stay legible on the dark fills, and both the group structure and the relative sizes become easy to read.
How to set treemap labels and fill colors and what it produces:
| Call | Result |
|---|---|
chart.Series.get(0).DataPoints |
Gets the data point collection, one point per rectangle |
points.get(i).DataFormat.Fill.FillType = xlsModule.ShapeFillType.SolidColor |
Makes that rectangle use a solid fill |
points.get(i).DataFormat.Fill.ForeColor = colors[i % colors.length] |
Assigns a fill color to that rectangle |
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true |
Enables category name labels, so item names appear on the rectangles |
chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping |
Overlays item names directly on the rectangles and leaves group names in the group corner |
xlsModule.ExcelTreeMapLabelOption.Banner |
Gives group names a strip at the top of each group and keeps item names inside the rectangles |
xlsModule.ExcelTreeMapLabelOption.None |
Hides every label in the chart |
The steps are:
- Load the test workbook and create the chart as described in the previous section, keeping the data range, position, and title unchanged.
- Get the data points with
chart.Series.get(0).DataPoints, loop over the data rows, and setDataFormat.Fill.FillTypetoxlsModule.ShapeFillType.SolidColorandForeColorto the chosen color for each point. - Set
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryNametotrue. Data labels are off by default on a treemap, so category names appear on the rectangles only after this is enabled. - Set
chart.Series.get(0).DataFormat.TreeMapLabelOptiontoxlsModule.ExcelTreeMapLabelOption.Overlappingso item names are drawn directly on the rectangles; useBannerinstead when group names should take a strip at the top of each group. - Write
TreeMapStyled.xlsxwithworkbook.SaveToFileand trigger the download, then release the workbook withworkbook.Dispose.
Below is the complete example. The chart setup is kept from the previous section so that it runs on its own; the additions are the fill colors and the labels:
function App() {
const styleTreeMap = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TreeMapData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Add a tree map chart and set its data range
const chart = sheet.Charts.Add();
chart.ChartType = xlsModule.ExcelChartType.TreeMap;
chart.DataRange = sheet.Range.get('A2:C11');
chart.TopRow = 1;
chart.BottomRow = 20;
chart.LeftColumn = 1;
chart.RightColumn = 12;
chart.ChartTitle = 'Area by cities';
// Prepare the fill colors used in the loop
const colors = [
xlsModule.Color.get_DarkGreen(),
xlsModule.Color.get_DarkBlue(),
xlsModule.Color.get_DarkGoldenrod(),
xlsModule.Color.get_DarkRed(),
xlsModule.Color.get_DarkSlateGray(),
];
// Set the fill color of every rectangle in data point order (10 data rows)
const points = chart.Series.get(0).DataPoints;
for (let i = 0; i < 10; i++) {
points.get(i).DataFormat.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
points.get(i).DataFormat.Fill.ForeColor = colors[i % colors.length];
}
// Enable category name labels and overlay the item names on the rectangles
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping;
// Save the workbook with the tree map chart into the VFS
const outputFileName = 'TreeMapStyled.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Create a Tree Map Chart in React</h1>
<button id="style-treemap" onClick={styleTreeMap}>Set Labels and Fill Colors</button>
</div>
);
}
export default App;
The workbook after setting treemap labels and fill colors:

FAQ
Treemap labels do not appear
Cause: Data labels are off by default on a treemap, so no names are drawn on the rectangles. TreeMapLabelOption only controls how labels are laid out, and setting it alone does not make labels visible.
Solution: Set DataLabels.HasCategoryName on the data points to true first, then set TreeMapLabelOption: use Overlapping to draw item names directly on the rectangles, or Banner to give group names a strip at the top of each group. For example:
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping;
How can the position and size of a treemap be adjusted
Cause: The position of a chart is controlled by TopRow, BottomRow, LeftColumn, and RightColumn, which mark the first and last row and column the chart covers.
Solution: Adjust these four properties before saving the workbook. Larger values widen the range of rows and columns the chart covers, so the chart takes up more space on the worksheet.
How should the data for a treemap be arranged
Cause: A treemap reads hierarchy and values by column, so the order of the columns determines how groups, items, and areas correspond to one another.
Solution: Arrange the data in three columns: the first for the category, the second for the item, and the third for the value, and keep the value column numeric. Start chart.DataRange at the first data row, for example sheet.Range.get("A2:C11"), so the header row is not included.
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.
In everyday data analysis, a scatter chart is an intuitive tool for examining the relationship between two numeric variables — for example, exploring the correlation between product price and sales, or income and spending. With a scatter chart, you can quickly spot how data points are distributed and any potential relationships. Spire.XLS for JavaScript creates scatter charts directly in the browser based on WebAssembly, and manages input/output files through a virtual file system (VFS), with no backend service required.
This article covers two core features:
For installation and project configuration, 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.
Create a Basic Scatter Chart
A scatter chart places two sets of numeric values on the horizontal and vertical axes, using the position of each point to show how the two variables correspond. Compared with reading a table, it makes clusters, gaps and upward or downward relationships easier to see. Mapping the source data onto the chart and fixing its position on the worksheet is all it takes to start examining that relationship.
Creating a basic scatter chart:
| Syntax | Result |
|---|---|
workbook.LoadFromFile(...) |
Loads the workbook that contains the source data |
sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers }) |
Adds a scatter chart with data markers to the worksheet |
chart.DataRange = sheet.Range.get("B2:B13") |
Sets the data range used by the chart |
chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13") |
Uses column A for the horizontal axis |
chart.Series.get(0).Values = sheet.Range.get("B2:B13") |
Uses column B for the vertical axis |
chart.LeftColumn / TopRow / RightColumn / BottomRow |
Positions the chart on the worksheet |
chart.ChartTitle, PrimaryCategoryAxis.Title, PrimaryValueAxis.Title |
Sets the chart title and the axis titles |
workbook.SaveToFile(...) |
Saves the workbook with the scatter chart to the VFS |
The steps are:
- Load the font file and the source data file into the VFS.
- Create a
Workbookobject, load the Excel document withWorkbook.LoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
Worksheets.Charts.Add()method with theScatterMarkerschart type to add a scatter chart. - Set the chart's
DataRangeandSeriesDataFromRange, and specify the horizontal and vertical axis data withSeries.get(0).CategoryLabelsandSeries.get(0).Values. - Set the chart position with the
LeftColumn,TopRow,RightColumnandBottomRowproperties. - Set the chart title with the
ChartTitleproperty, and set the X-axis and Y-axis titles with thePrimaryCategoryAxis.TitleandPrimaryValueAxis.Titleproperties. - Use the
Workbook.SaveToFile()method to save the document, then release resources withDispose().
Here is a complete code example showing how to create a basic scatter chart in React:
function App() {
const createScatterChart = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and Excel file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ScatterChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Add a scatter chart with data markers
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers });
// Set the chart's data range
chart.DataRange = sheet.Range.get("B2:B13");
chart.SeriesDataFromRange = false;
// Set the series data: column A for the X-axis and column B for the Y-axis
chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13");
chart.Series.get(0).Values = sheet.Range.get("B2:B13");
// Set the position of the chart
chart.LeftColumn = 1;
chart.TopRow = 15;
chart.RightColumn = 10;
chart.BottomRow = 32;
// Set the chart title
chart.ChartTitle = "Scatter Chart";
// Set the X-axis and Y-axis titles
chart.PrimaryCategoryAxis.Title = "Car Price";
chart.PrimaryValueAxis.Title = "Salary";
// Save the document
const outputFileName = 'CreateScatterChart_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Create Scatter Chart</h1>
<button onClick={createScatterChart}>
Start
</button>
</div>
);
}
export default App;
The chart after creating a basic scatter chart:

Add a Trend Line to the Scatter Chart
A trend line adds a fitted curve on top of the scatter points to summarize the overall direction of the data. The more scattered the points are, the more useful the trend line becomes for judging whether the variables rise, fall or stay roughly flat together. Adding an exponential trend line to the chart's data series records that trend in the workbook.
Adding a trend line to the scatter chart:
| Syntax | Result |
|---|---|
sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers }) |
Adds a scatter chart with data markers |
chart.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Exponential }) |
Adds an exponential trend line to the first series |
workbook.SaveToFile(...) |
Saves the workbook with the trend line to the VFS |
The steps are:
- Load the font file and the source data file into the VFS.
- Create a
Workbookobject, load the Excel document withLoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
Worksheets.Charts.Add()method to add a scatter chart, and setDataRange,CategoryLabelsandValues. - Set the chart title with the
ChartTitleproperty, and set the X-axis and Y-axis titles with the axis title properties. - Use the
Series.get(0).TrendLines.Add()method to add an exponential trend line to the first data series. - Use the
Workbook.SaveToFile()method to save the document, then release resources withDispose().
Here is a complete code example showing how to add an exponential trend line to a scatter chart in React:
function App() {
const addTrendLine = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and Excel file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'ScatterChartData.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Add a scatter chart
let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers });
chart.DataRange = sheet.Range.get("B2:B13");
chart.SeriesDataFromRange = false;
chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13");
chart.Series.get(0).Values = sheet.Range.get("B2:B13");
// Set the position of the chart
chart.LeftColumn = 1;
chart.TopRow = 15;
chart.RightColumn = 10;
chart.BottomRow = 32;
// Set the chart title
chart.ChartTitle = "Scatter Chart with Trend Line";
// Set the X-axis and Y-axis titles
chart.PrimaryCategoryAxis.Title = "Car Price";
chart.PrimaryValueAxis.Title = "Salary";
// Add an exponential trend line to the first data series
chart.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Exponential });
// Save the document
const outputFileName = 'ScatterChartTrendLine_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Trend Line to Scatter Chart</h1>
<button onClick={addTrendLine}>
Start
</button>
</div>
);
}
export default App;
The scatter chart after adding a trend line:

FAQ
Data points are all clustered at the bottom-left of the axes
Cause: The data range set for the chart includes the header row or empty cells. These non-numeric contents are treated as zero or ignored, so all data points cluster at the bottom-left of the axes.
Solution: Make sure the chart data range starts from the data rows (for example sheet.Range.get("A2:A13")), and that CategoryLabels and Values point to the same row range so the X and Y values correspond one-to-one.
Adding an exponential trend line fails
Cause: An exponential trend line requires all data values to be positive. When the data contains zero or negative values, the exponential regression fit cannot be completed, and the trend line may fail to be added or may not be displayed.
Solution: Check whether the data contains zero or negative values. If necessary, use a trend line type that fits the current data, such as linear (Linear) or polynomial (Polynomial).
How do the horizontal and vertical axis values correspond?
Cause: A scatter chart takes the horizontal axis from CategoryLabels and the vertical axis from Values. If the two ranges cover different rows or start at different rows, the points are paired incorrectly and may even fall outside the plot area.
Solution: Point CategoryLabels and Values at the same row range, for example rows 2 through 13, and change only the column. Each horizontal value then has a matching vertical value.
Obtain 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.
The header and footer in Excel are text or images placed at the top and bottom of every printed page. The text or images in the header and footer can display basic information about the page or document, such as the file name, company logo, page number, date and time. Spire.XLS for JavaScript completes this directly in the browser based on WebAssembly, managing input/output files through a virtual file system (VFS), with no backend service required.
The PageSetup class provided by Spire.XLS for JavaScript can configure the page of an Excel workbook, including setting the header and footer. The LeftHeader, CenterHeader, RightHeader, LeftFooter, CenterFooter, RightFooter properties in this class add content to the left, center and right of the header or footer. To add fields to the header or footer or to set the text format of the header and footer, you can use the format codes listed in the table below.
| Code | Description |
|---|---|
&P |
Current page number |
&N |
Total number of pages |
&D |
Current date |
&T |
Current time |
&G |
Picture |
&A |
The worksheet name |
&F |
The file name |
&B |
Bold text |
&I |
Italic text |
&U |
Underline text |
&"Font Name" |
The font name, e.g. &"Arial" |
& + Integer |
The font size, e.g. &12 |
&K + 16-digit color code |
The font color, e.g. &KFF0000 |
This article introduces two core features:
For installation and project configuration, 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.
Add an Image and Formatted Text to the Header
The header sits at the top of every page and is a natural place for a company logo, a document name or other fixed information. Putting the image on the left and text with its own font and color on the right gives every printed or previewed page the same identification without adding it page by page.
Adding an image and formatted text to the header:
| Syntax | Result |
|---|---|
new xlsModule.Stream(imgFileName) |
Reads the image stream from the VFS |
sheet.PageSetup.LeftHeaderImage = image |
Sets the image as the left header image |
sheet.PageSetup.LeftHeader = "&G" |
Displays the image in the left header |
sheet.PageSetup.RightHeader = "&\"Arial\"&B&10&K4253E2..." |
Writes text with the specified font, bold, size and color in the right header |
sheet.ViewMode = xlsModule.ViewMode.Layout |
Switches to Page Layout view for previewing |
workbook.SaveToFile(...) |
Saves the workbook with the header to the VFS |
The steps are:
- Load the font, Excel file and image into the VFS.
- Create a
Workbookobject, load the Excel file withLoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Use
Streamto load the image, and set it as the image source of the left header through thePageSetup.LeftHeaderImageproperty. - Use the
PageSetup.LeftHeaderproperty to display the image in the left header. - Use the
PageSetup.RightHeaderproperty to insert formatted text in the right header. - Switch the worksheet to Page Layout view with
ViewMode.Layoutto preview the header. - Use the
Workbook.SaveToFile()method to save the workbook, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to add an image and formatted text to the header of a worksheet in React:
function App() {
const addImageAndTextToHeader = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font, Excel file and image into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'HeaderFooter.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
const imgFileName = 'logo.png';
await window.spire.FetchFileToVFS(imgFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Load the image and set it as the image source of the left header
const image = new xlsModule.Stream(imgFileName);
sheet.PageSetup.LeftHeaderImage = image;
sheet.PageSetup.LeftHeader = "&G";
// Add formatted text to the right header
sheet.PageSetup.RightHeader = "&\"Arial\"&B&10&K4253E2Ice Blue Technology Co., Ltd. \n www.e-iceblue.com";
// Switch to Page Layout view to preview the header and footer
sheet.ViewMode = xlsModule.ViewMode.Layout;
// Save the document
const outputFileName = 'HeaderImageAndText_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Image and Formatted Text to Header</h1>
<button onClick={addImageAndTextToHeader}>
Start
</button>
</div>
);
}
export default App;
The worksheet after adding an image and formatted text to the header:

Add the Current Date and Page Number to the Footer
The footer sits at the bottom of every page and is commonly used for page numbers and dates. Both update automatically as pages are printed, so there is no need to maintain them in the sheet body; placing them on the left and right also helps readers find their position when paging through a report.
Adding the current date and page number to the footer:
| Syntax | Result |
|---|---|
sheet.PageSetup.LeftFooter = "...&P..." |
Shows the current page number in the left footer |
sheet.PageSetup.RightFooter = "...&D" |
Shows the current date in the right footer |
sheet.ViewMode = xlsModule.ViewMode.Layout |
Switches to Page Layout view for previewing |
workbook.SaveToFile(...) |
Saves the workbook with the footer to the VFS |
The steps are:
- Load the font and Excel file into the VFS.
- Create a
Workbookobject, load the Excel file withLoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Use the
PageSetup.LeftFooterproperty to add a formatted page number to the left footer, customizing its format as you like. - Use the
PageSetup.RightFooterproperty to add the current date to the right footer, and change the appearance of the date text as needed. - Switch the worksheet to Page Layout view with
ViewMode.Layoutto preview the footer. - Use the
Workbook.SaveToFile()method to save the workbook, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to add the current date and page number to the footer of a worksheet in React:
function App() {
const addDateAndPageNumberToFooter = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and Excel file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'HeaderFooter.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Add the page number to the left footer; &P represents the current page number
sheet.PageSetup.LeftFooter = "&\"Arial\"&B&10&K4253E2Page &P";
// Add the current date to the right footer; &D represents the current date
sheet.PageSetup.RightFooter = "&\"Arial\"&B&10&K4253E2&D";
// Switch to Page Layout view to preview the header and footer
sheet.ViewMode = xlsModule.ViewMode.Layout;
// Save the document
const outputFileName = 'FooterDatePageNumber_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Current Date and Page Number to Footer</h1>
<button onClick={addDateAndPageNumberToFooter}>
Start
</button>
</div>
);
}
export default App;
The worksheet after adding the current date and page number to the footer:

FAQ
Headers and footers are not visible in Excel
Cause: An Excel worksheet is in Normal view by default, and headers and footers are only displayed in Page Layout view or when printing.
Solution: Switch the worksheet to Page Layout view in code with sheet.ViewMode = xlsModule.ViewMode.Layout;, or switch to the "Page Layout" view manually in Excel.
The picture in the header is not displayed
Cause: Only the image source (such as LeftHeaderImage) was set, but the &G placeholder was not used in the corresponding header text, or the image file was not loaded into the VFS correctly.
Solution: Make sure the image has been loaded into the VFS with FetchFileToVFS() and add &G to the header string, for example sheet.PageSetup.LeftHeader = "&G";.
How can the current page number and total page count appear together?
Cause: The footer used only the &P field, so it shows the current page number without any information about the total number of pages.
Solution: Put both &P and &N in the footer string, for example sheet.PageSetup.LeftFooter = "&P / &N";. When printing or previewing, the two fields are replaced with the current page number and the total page count respectively.
Obtain 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.
Add and Remove Watermarks in Excel with JavaScript in React
2026-10-23 08:49:10 Written by liu taliaExcel does not have a built-in watermark feature, but in scenarios such as contracts, reports, and internal documents, we often need to mark files with identifiers like "Confidential" or "Sample" to prevent information from being copied and distributed at will. Spire.XLS for JavaScript processes Excel documents directly in the browser based on WebAssembly, and manages input and output files through a virtual file system (VFS), with no backend service required. With the header image feature, you can easily add and remove watermarks in worksheets.
This article covers two core features:
For installation and project configuration, 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.
Add a Watermark to an Excel Worksheet
Excel does not have a built-in watermark feature, yet contracts, reports and internal documents often need to mark their origin or scope of use. A watermark lays pale text across the center of the page, leaving the body readable while signaling what the document is for. Drawing the watermark as an image the size of the page and placing it in the header makes it appear on every page.
Adding a watermark to an Excel worksheet:
| Syntax | Result |
|---|---|
sheet.PageSetup.PageWidth / PageHeight |
Gets the page size used to draw a matching watermark image |
watermarkCanvas.toBlob(...) and FS.writeFile(...) |
Creates the watermark image and writes it to the VFS |
new xlsModule.Stream(imgFileName) |
Reads the watermark image stream from the VFS |
sheet.PageSetup.LeftHeaderImage = watermark |
Sets the watermark image as the left header image |
sheet.PageSetup.LeftHeader = "&G" |
Displays the image in the header |
sheet.ViewMode = xlsModule.ViewMode.Layout |
Switches to Page Layout view for previewing |
workbook.SaveToFile(...) |
Saves the watermarked workbook to the VFS |
The steps are:
- Load the font file and the input file into the VFS.
- Create a
Workbookobject, load the Excel document withLoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Use the Canvas API to draw a watermark image that matches the page size: a white background with centered LightCoral text rotated -45°.
- Save the watermark image to the virtual file system (VFS) and load it with a
Streamobject. - Set the watermark image as the left header image (
LeftHeaderImage) and set the header text to the image placeholder&G. - Switch the worksheet to Page Layout view with
ViewMode.Layoutto preview the watermark effect. - Use the
Workbook.SaveToFile()method to save the document, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to add a watermark to a worksheet in React:
function App() {
const addWatermark = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the Excel file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Watermark.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Define the watermark text, text color, and background color
const watermarkText = 'Internal Use';
const textColor = 'lightcoral';
const backColor = 'white';
// Draw the watermark image according to the page size (white background with centered text rotated -45°)
const drawWatermarkImage = (text, color, bg, width, height) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the background
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
// Set the text style and draw the rotated text at the center of the page
ctx.fillStyle = color;
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 4);
ctx.fillText(text, 0, 0);
ctx.restore();
return canvas;
};
// Draw the watermark image and write it to the virtual file system (VFS)
const watermarkCanvas = drawWatermarkImage(watermarkText, textColor, backColor, sheet.PageSetup.PageWidth, sheet.PageSetup.PageHeight);
const watermarkBlob = await new Promise((resolve) => watermarkCanvas.toBlob(resolve, 'image/png'));
const watermarkBytes = new Uint8Array(await watermarkBlob.arrayBuffer());
const imgFileName = 'Watermark.png';
window.dotnetRuntime.Module.FS.writeFile(imgFileName, watermarkBytes);
// Load the watermark image
const watermark = new xlsModule.Stream(imgFileName);
// Set the watermark image as the left header image and display it with the &G placeholder
sheet.PageSetup.LeftHeaderImage = watermark;
sheet.PageSetup.LeftHeader = "&G";
// Switch to Page Layout view to preview the watermark effect
sheet.ViewMode = xlsModule.ViewMode.Layout;
// Save the document
const outputFileName = 'AddWatermark_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Add Watermark to Excel</h1>
<button onClick={addWatermark}>
Start
</button>
</div>
);
}
export default App;
The worksheet after adding a watermark:

Remove a Watermark from an Excel Worksheet
Because the watermark is carried by the header, removing it means clearing the reference in that header. Once the header text is empty, the image is no longer displayed and the body of the worksheet stays as it is.
Removing a watermark from an Excel worksheet:
| Syntax | Result |
|---|---|
workbook.LoadFromFile(...) |
Loads the watermarked workbook |
workbook.Worksheets.get(0) |
Gets the first worksheet to process |
sheet.PageSetup.LeftHeader = "" |
Clears the left header text and the image placeholder |
workbook.SaveToFile(...) |
Saves the workbook without the watermark to the VFS |
The steps are:
- Load the watermarked Excel file into the VFS.
- Create a
Workbookobject, load the document withLoadFromFile(), and get the first worksheet withWorkbook.Worksheets.get(0). - Set
PageSetup.LeftHeaderto an empty string to remove the watermark image placeholder from the header. - Use the
Workbook.SaveToFile()method to save the document, then release resources withWorkbook.Dispose().
Here is a complete code example showing how to remove a watermark from a worksheet in React:
function App() {
const removeWatermark = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check whether the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the watermarked Excel file into the VFS
const inputFileName = 'AddWatermark.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Get the first worksheet
const sheet = workbook.Worksheets.get(0);
// Clear the left header text to remove the watermark
sheet.PageSetup.LeftHeader = "";
// Save the document
const outputFileName = 'RemoveWatermark_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// Release resources
workbook.Dispose();
// Read the converted file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Remove Watermark from Excel</h1>
<button onClick={removeWatermark}>
Start
</button>
</div>
);
}
export default App;
The worksheet after removing the watermark:

FAQ
The watermark is not visible in Normal view
Cause: A watermark implemented through a header image is only displayed in Page Layout view and print preview, and is not visible in Excel's Normal view.
Solution: Switch the worksheet view to Page Layout mode to preview the watermark, for example by setting sheet.ViewMode = xlsModule.ViewMode.Layout in the code.
The watermark position is different from expected
Cause: A header image watermark is displayed in the header area, and its horizontal position can be controlled by the left, center, and right header positions. If the position does not match expectations, it is usually because the header position set is inconsistent with the requirement.
Solution: Choose LeftHeaderImage, CenterHeaderImage, or RightHeaderImage to set the watermark image according to your needs, and set the corresponding image placeholder &G with the LeftHeader, CenterHeader, or RightHeader property.
Residual content remains in the file after removing the watermark
Cause: The watermark is implemented through a header image. If only part of the header area is cleared, other areas (such as the left or right header) may still contain the watermark image.
Solution: Make sure to clear the text of all header areas where the watermark was set, including LeftHeader, CenterHeader, and RightHeader.
Obtain 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.
The same PDF can look different when opened on different machines: if the font a document references is not installed locally, the viewer substitutes another one. Conversely, when brand standards or legal requirements say an outgoing file may only use specified fonts, you first need to know which fonts the original uses. Common browser-side PDF libraries mostly draw pages; they do not expose font resources.
This article uses Spire.PDF for JavaScript to read and replace the fonts in a PDF: first list the document's fonts with PdfDocument.UsedFonts, then replace the standard and non-embedded TrueType fonts with a new TrueType font via PdfUsedFont.Replace(). Everything runs in the browser; files are read and written through a virtual file system (VFS) and never uploaded to a server.
This article covers two core features:
For installation and project configuration, see Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.
Get the Fonts Used in a PDF
Spire.PDF for JavaScript provides the PdfDocument.UsedFonts property for reading every font used in a document. Each item in the collection is a PdfUsedFont, whose Name, Size, Style, and Type give the font name, size, style, and font type.
The steps are:
- Get the Spire.PDF WASM module and confirm it is ready.
- Load the sample PDF into the VFS, create a
PdfDocumentobject, and load the file. - Read
PdfDocument.UsedFontsand take eachPdfUsedFontfrom the collection. - Put together each font's name, size, style, and type, and write them to a text file.
- Read the result file from the VFS and trigger a browser download.
The code for getting the fonts in a PDF is as follows:
function App() {
const getFonts = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the sample PDF into the VFS
const inputFileName = 'Font_Sample.pdf';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Create a PdfDocument object and load the document
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// UsedFonts lists the fonts the document currently uses
const fonts = doc.UsedFonts;
const styleNames = new Map([
[pdfModule.PdfFontStyle.Regular.value, 'Regular'],
[pdfModule.PdfFontStyle.Bold.value, 'Bold'],
[pdfModule.PdfFontStyle.Italic.value, 'Italic'],
[pdfModule.PdfFontStyle.Underline.value, 'Underline'],
[pdfModule.PdfFontStyle.Strikeout.value, 'Strikeout'],
]);
const typeNames = new Map([
[pdfModule.PdfFontType.Standard.value, 'Standard'],
[pdfModule.PdfFontType.TrueType.value, 'TrueType'],
[pdfModule.PdfFontType.TrueTypeEmbedded.value, 'Embedded TrueType'],
]);
// Collect each font's name, size, style, and type
let report = `Document: ${inputFileName}\r\nFont count: ${fonts.length}\r\n\r\n`;
for (let i = 0; i < fonts.length; i++) {
const font = fonts.get(i);
report += `${i + 1}. ${font.Name}\r\n`;
report += ` Size: ${font.Size}\r\n`;
report += ` Style: ${styleNames.get(font.Style.value)}\r\n`;
report += ` Type: ${typeNames.get(font.Type.value)}\r\n\r\n`;
}
// Write the font list into the VFS
const outputFileName = 'FontList.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
doc.Close();
// Read the result file from the VFS and trigger the download
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
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>Get the Fonts Used in a PDF</h1>
<button onClick={getFonts}>
Get fonts
</button>
</div>
);
}
export default App;
The font list is written to a text file, listing each font's name, size, style, and type:

Replace the Fonts Used in a PDF
Spire.PDF for JavaScript also provides the PdfUsedFont.Replace() method, which swaps one font in a document for another font object. It works only with standard Type1 fonts and non-embedded TrueType fonts; embedded fonts (such as subset-embedded CJK fonts) cannot be replaced and must be skipped.
The steps are:
- Get the Spire.PDF WASM module and confirm it is ready.
- Load the sample PDF and the replacement font file into the VFS, with the font file placed under
/Library/Fonts/. - Create a
PdfDocumentobject, load the PDF, and readPdfDocument.UsedFonts. - Create a
PdfTrueTypeFontobject, iterate overUsedFonts, skip embedded fonts, and callPdfUsedFont.Replace()on standard and non-embedded TrueType fonts. - Save the result with
PdfDocument.SaveToFile()and trigger a browser download.
The code for replacing the fonts in a PDF is as follows:
function App() {
const replaceFonts = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the replacement font and the sample PDF
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Font_Sample.pdf';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// Create a PdfDocument object and load the document
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// UsedFonts returns the fonts used in the document
const fonts = doc.UsedFonts;
const newFont = new pdfModule.PdfTrueTypeFont({
fontFamily: 'Arial Unicode MS',
size: 12,
style: pdfModule.PdfFontStyle.Regular,
unicode: true
});
// Replace one by one: Replace() supports only standard Type1 and non-embedded TrueType fonts
for (let i = 0; i < fonts.length; i++) {
const font = fonts.get(i);
// Embedded fonts cannot be replaced; skip them
if (font.Type.value === pdfModule.PdfFontType.TrueTypeEmbedded.value) {
continue;
}
font.Replace(newFont);
}
// Save the result file
const outputFileName = 'ReplacedFonts.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// Read the generated file from the VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Replace the Fonts Used in a PDF</h1>
<button onClick={replaceFonts}>
Replace fonts
</button>
</div>
);
}
export default App;
After replacement, the text in the standard font uses the new TrueType font while embedded fonts stay unchanged:

FAQ
Why do the text size and style change after replacement
Reason: Replace() takes a complete font object, and replacement applies the new object's own size and style; the original sizes of individual text runs are not preserved.
Solution: Set the target font's size and style to the values you want in the result. For example, to make everything 12 pt regular, pass size: 12 and style: PdfFontStyle.Regular to PdfTrueTypeFont.
Why do Chinese or Japanese characters turn into boxes or question marks after replacement
Reason: The target font has no glyphs for those characters, or PdfTrueTypeFont was created without Unicode enabled.
Solution: Use a font that covers the target language (such as Arial Unicode MS) and pass unicode: true when creating it. The font file must first be loaded into /Library/Fonts/ in the VFS so Spire.PDF can embed it.
Can I replace only one of the fonts
Reason: UsedFonts lists each font in the document, and Replace() acts on a single PdfUsedFont.
Solution: Check Name while iterating and replace only the matching font:
for (let i = 0; i < fonts.length; i++) {
const font = fonts.get(i);
if (font.Name === 'ArialMT') {
font.Replace(newFont);
}
}
Font names of subset-embedded fonts may carry prefixes such as BCDEEE+, so write the comparison against the Name you actually read from the list.
Why does Replace() report that the font is not a standard Type 1 or non-embedded TrueType font
Reason: PdfUsedFont.Replace() only works on standard Type1 fonts and non-embedded TrueType fonts. CJK fonts in PDFs are often subset-embedded TrueType fonts (UsedFonts reports them as Embedded TrueType), and Spire.PDF cannot replace them directly.
Solution: Check Type while iterating UsedFonts and call Replace() only on standard and non-embedded TrueType fonts; skip the rest, or prepare a document that uses standard or non-embedded fonts first.
Get a Free License
To remove the evaluation message from result documents or lift the feature limits, contact sales for a 30-day temporary license.
When a product manual or conference handout is printed as a booklet, the pages of a multi-page document have to be rearranged onto facing sheets in saddle-stitch order. In desktop software that means dragging pages by hand, which goes wrong easily once the page count grows, and every new document starts over. Hand it to a server, and the file has to leave the user's device first.
This article uses Spire.PDF for JavaScript to create PDF booklets. It loads, imposes, and saves PDFs in the browser via WebAssembly, reading and writing every file through a virtual file system (VFS) with no backend involved.
This article covers two core features:
For installation and project configuration, see Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.
Impose a PDF as a Two-Sided Booklet Automatically
Spire.PDF for JavaScript provides the PdfDocument.CreateBooklet() method, which imposes the pages of an existing PDF onto facing pages in saddle-stitch order. The width and height parameters set the sheet size of the finished product; when bothSides is true, the layout is arranged for double-sided printing.
The steps are:
- Get the Spire.PDF WASM module and confirm it is ready.
- Load the source PDF into the VFS.
- Create a
PdfDocumentobject and callCreateBooklet()with a width equal to two A4 pages side by side and the height of one A4 page. - Save the result with
SaveToFile(). - Read the result file from the VFS and trigger a browser download.
The code for creating a booklet by automatic imposition is as follows:
function App() {
const createBooklet = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check that the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the source PDF into the VFS
const inputFileName = 'CMS-Technical-Manual.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Spread size: width is two A4 pages side by side, height is one A4 page
const width = pdfModule.PdfPageSize.A4().Width * 2;
const height = pdfModule.PdfPageSize.A4().Height;
// Create a document and impose the pages in saddle-stitch order
const doc = new pdfModule.PdfDocument();
doc.CreateBooklet({ fileName: inputFileName, width: width, height: height, bothSides: true });
// Save the result file
const outputFileName = 'Booklet_Auto.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// Read the generated file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Create a Two-Sided Booklet</h1>
<button onClick={createBooklet}>
Create Booklet
</button>
</div>
);
}
export default App;
The source pages are imposed onto spreads two at a time in saddle-stitch order:

Build Facing Pages in a Custom Page Order
Spire.PDF for JavaScript also provides the PdfPageBase.CreateTemplate() method, which extracts the content of a page into a template that PdfTemplate.Draw() can draw at a given position on a new page. A page-order array decides which two pages land on each sheet; the example puts consecutive pages side by side in reading order.
The steps are:
- Get the Spire.PDF WASM module and confirm it is ready.
- Load the source PDF into the VFS.
- Create a
PdfDocumentobject and load the source document; then create an output document whose width is two A4 pages side by side and whose height is one A4 page. - For each pair in the page order, take a page template with
CreateTemplate()and draw it onto the left or right half of the spread withDraw(). - Save the result with
SaveToFile(), then read it from the VFS and trigger a download.
The code for building facing pages manually is as follows:
function App() {
const createCustomBooklet = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check that the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the source PDF into the VFS
const inputFileName = 'CMS-Technical-Manual.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Create a PdfDocument object and load the source document
const srcDoc = new pdfModule.PdfDocument();
srcDoc.LoadFromFile(inputFileName);
const pageCount = srcDoc.Pages.Count;
const a4 = pdfModule.PdfPageSize.A4();
// Output document: width is two A4 pages side by side, height is one A4 page
const bookletDoc = new pdfModule.PdfDocument();
bookletDoc.PageSettings.Margins.All = 0;
bookletDoc.PageSettings.Width = a4.Width * 2;
bookletDoc.PageSettings.Height = a4.Height;
// Two consecutive pages per sheet: 1|2, 3|4, 5|6, ...
for (let i = 0; i < pageCount; i += 2) {
const sheet = bookletDoc.Pages.Add();
// Left half
const leftTemplate = srcDoc.Pages.get_Item(i).CreateTemplate();
leftTemplate.Draw({ page: sheet, location: new pdfModule.PointF(0, 0) });
// Right half; with an odd page count the last sheet leaves the right side blank
if (i + 1 < pageCount) {
const rightTemplate = srcDoc.Pages.get_Item(i + 1).CreateTemplate();
rightTemplate.Draw({ page: sheet, location: new pdfModule.PointF(a4.Width, 0) });
}
}
// Save the result file
const outputFileName = 'Booklet_CustomOrder.pdf';
bookletDoc.SaveToFile(outputFileName);
bookletDoc.Close();
srcDoc.Close();
// Read the generated file from the VFS and trigger a 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Custom Page Order Imposition</h1>
<button onClick={createCustomBooklet}>
Build Facing Pages
</button>
</div>
);
}
export default App;
Two consecutive pages sit side by side on one sheet; a 6-page document produces 3 spreads:

FAQ
What determines the paper size of the booklet
Reason: The width and height passed to CreateBooklet() describe the sheet size of the finished spread, not the size of a single page in the source document. This article uses "A4 width × 2" and "A4 height", which gives an A3-sized spread with one A4 page on each half.
Solution: To print on A4 paper, set the sheet to A4 landscape — width equal to the A4 height, height equal to the A4 width; each half then holds an A5 page, and the source pages are scaled down to half a sheet.
// A4 landscape: one A5 page on each half
const width = pdfModule.PdfPageSize.A4().Height;
const height = pdfModule.PdfPageSize.A4().Width;
What does the bothSides parameter do
Reason: bothSides tells CreateBooklet() whether the result will be printed double-sided. true groups pages into front and back pairs in saddle-stitch order and pads the page count to a multiple of 4; false lays pages out for single-sided printing and adds no blank pages.
Solution: Pass true when you print on a duplex printer and fold and staple the result; pass false for single-sided printing only. The print mode must match the imposed layout, or the page order will not line up.
How is the booklet arranged when the page count is not a multiple of 4
Reason: A saddle-stitch sheet holds four pages (two on each side); when the pages run out, the last sheet has blank positions.
Solution: With automatic imposition, CreateBooklet() pads the required spreads. With manual imposition, the loop checks i + 1 < pageCount before drawing the right half, so an unpaired page simply leaves the right side blank.
In manual imposition the content drifts to one side or runs off the sheet
Reason: PdfTemplate.Draw() draws at the template's original size by default. If a source page is wider than half a sheet, the right-hand page crosses the center line or extends past the edge.
Solution: Make the source page size match half the sheet. In this article the source pages are A4 and the spread is two A4 pages wide, so placing them at x = 0 and x = A4.Width aligns exactly. If the source size differs, use PdfCanvas.DrawTemplate() with size set to the half-sheet size; the template is scaled to fit.
sheet.Canvas.DrawTemplate({
template: rightTemplate,
location: new pdfModule.PointF(a4.Width, 0),
size: new pdfModule.SizeF(a4.Width, a4.Height)
});
Get a Free License
If you want to remove the evaluation message from the result document or lift the feature limits, contact sales for a temporary license valid for 30 days.
When a workbook holds several worksheets, default names and look-alike tabs make it hard to tell one sheet from another. Renaming “Sheet1” to “Quarterly Sales” tells a colleague at a glance what the sheet contains; marking the sales, inventory and summary sheets with red, green and light blue tabs removes the need to open each one while switching. Both the name and the tab color live inside the workbook itself, so they survive distribution and archiving without any external notes. Spire.XLS for JavaScript performs these operations in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.
This article covers two core features:
For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialized.
Rename Excel Worksheets
A worksheet name is the first identifier a reader sees after opening the file. With default names such as “Sheet1” and “Sheet2”, even a well-filled workbook has to be opened sheet by sheet before the right table turns up; with a business name such as “Quarterly Sales”, the tab strip itself becomes a table of contents, and the label is preserved when the file is printed or exported. The sample workbook contains sales, inventory and summary data in that order. This section renames the first worksheet.
How to rename a worksheet and what it produces:
| Call | Result |
|---|---|
workbook.Worksheets.get(0) |
Takes the first worksheet by index |
sheet.Name |
Reads back the current worksheet name |
sheet.Name = 'Quarterly Sales' |
Assigns the new worksheet name |
workbook.SaveToFile |
Saves the renamed workbook to the VFS |
The steps are:
- Load the font file and the test data file into the VFS with
window.spire.FetchFileToVFS. - Create a workbook with
new Workbook()and load the input file withworkbook.LoadFromFile. - Take the first worksheet with
workbook.Worksheets.get(0). - Assign the new name to
sheet.Name. - Save the workbook with
workbook.SaveToFile, then release the workbook object withworkbook.Dispose.
The complete example below renames an Excel worksheet in React:
function App() {
const renameWorksheet = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'WorksheetNames.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and take the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Assign the new worksheet name
sheet.Name = 'Quarterly Sales';
// Save the workbook
const outputFileName = 'RenameWorksheet.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Rename Excel Worksheets</h1>
<button id="rename-worksheet" onClick={renameWorksheet}>Rename Worksheet</button>
</div>
);
}
export default App;
The workbook after renaming the first worksheet:

Set Excel Worksheet Tab Colors
Even with descriptive names, the tab strip remains a row of similarly sized text, which has to be scanned sheet by sheet. Tab colors add a second identifier: red for sales, green for inventory and light blue for summary. Color is perceived before the text is read, so a reader can locate a group directly, and both the color and the name stay in the workbook when the file is shared. The three worksheets in the sample correspond to the three groups, so this section assigns each one a different tab color.
How to set worksheet tab colors and what it produces:
| Call | Result |
|---|---|
workbook.Worksheets.get(0) |
Takes the worksheet whose tab will be colored |
sheet.TabColor = xlsModule.Color.get_Red() |
Turns the worksheet tab red |
sheet.TabColor = xlsModule.Color.get_Green() |
Turns the worksheet tab green |
sheet.TabColor = xlsModule.Color.get_LightBlue() |
Turns the worksheet tab light blue |
xlsModule.Color.FromArgb(255, 255, 193, 7) |
Builds a custom RGB color for TabColor |
The steps are:
- Load the font file and the test data file into the VFS with
window.spire.FetchFileToVFS. - Create a workbook with
new Workbook()and load the input file withworkbook.LoadFromFile. - Take the three worksheets in order with
workbook.Worksheets.get(0),get(1)andget(2). - Assign
xlsModule.Color.get_Red(),get_Green()andget_LightBlue()to each worksheet'sTabColor. - Save the workbook with
workbook.SaveToFile, then release the workbook object withworkbook.Dispose.
The complete example below sets worksheet tab colors in React:
function App() {
const setTabColor = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check that the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the test data file into the VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'WorksheetNames.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// Set the tab colours to red, green and light blue in worksheet order
workbook.Worksheets.get(0).TabColor = xlsModule.Color.get_Red();
workbook.Worksheets.get(1).TabColor = xlsModule.Color.get_Green();
workbook.Worksheets.get(2).TabColor = xlsModule.Color.get_LightBlue();
// Save the workbook
const outputFileName = 'SetTabColor.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// Dispose of the workbook to release its resources
workbook.Dispose();
// Read the result back out of the VFS and trigger the 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);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Excel Worksheet Tab Colors</h1>
<button id="set-tab-color" onClick={setTabColor}>Set Tab Colors</button>
</div>
);
}
export default App;
The workbook after setting tab colors for the three worksheets:

Set a Custom Tab Color
When the built-in colors do not fit a report's palette, Color.FromArgb builds a custom color. Its four arguments are the alpha, red, green and blue channels, each from 0 to 255:
// Set the first worksheet tab to amber
workbook.Worksheets.get(0).TabColor = xlsModule.Color.FromArgb(255, 255, 193, 7);
FAQ
Worksheet rename reports a duplicate name
Cause: Worksheet names in a workbook must be unique, and the comparison ignores case. Assigning a name already used by another worksheet, such as “Sales” and “sales”, triggers the limit.
Solution: Before renaming, iterate over workbook.Worksheets, read each Name, and confirm the target name is free before assigning it to sheet.Name.
A sheet name containing a slash throws an error
Cause: Excel does not allow worksheet names to contain :, \, /, ?, *, [ or ]; Spire.XLS throws when such a name is assigned.
Solution: Replace the illegal characters with a hyphen or underscore before assigning the value to sheet.Name.
What happens when a worksheet name exceeds 31 characters
Cause: Excel limits worksheet names to 31 characters. Spire.XLS does not report an error; it truncates the name to 31 characters before writing it.
Solution: Check name.length before assigning and shorten the name when it is longer than 31 characters. The truncation happens during assignment, so comparing the saved name with the intended one will reveal the difference.
Getting a worksheet by name throws Arg_NullReferenceException
Cause: workbook.Worksheets.get('name') returns a worksheet only when the name matches exactly. A missing name, a trailing space, or a worksheet that has already been renamed all throw this exception.
Solution: Iterate over workbook.Worksheets and print each Name to confirm the current name before calling get. When the name comes from external input, trim leading and trailing spaces first.
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.