A workbook exported by a reporting system usually carries nothing but the system name as its author, with the title, subject and keywords left blank; teams meanwhile need to attach fields the export does not produce — an export batch, an owner, an approval state — for archiving and searching. None of this takes up a cell: it all lives in the workbook's document properties, which Excel shows under "File > Info > Properties". Spire.XLS for JavaScript writes them in the browser on top of WebAssembly, using a virtual file system (VFS) for input and output files, so no back-end service is needed.

This article covers three feature points:

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 initialised.


Set the summary properties of a workbook

The summary properties are the layer of description Excel shows in File Explorer and under "File > Info", and the first thing a search of the archive picks up. A workbook produced by a program usually has only the system name as its author and leaves the other entries blank; filling them in gives the file a readable context as it moves between people. The text entries take a plain assignment, while the two date entries have to be given a date object. The steps are:

  1. Load the workbook and take the summary property collection from workbook.DocumentProperties.
  2. Assign the text entries — Title, Subject, Author, Keywords, Comments and Category — directly.
  3. Assign Company and Manager the same way; Excel carries these two in its extended properties.
  4. Set CreatedTime and LastSaveTime to Date objects.
  5. Save the workbook.

Here is a complete code example showing how to set the summary properties of a workbook in React:

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

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

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // Set the text entries of the summary properties
    const summary = workbook.DocumentProperties;
    summary.Title = 'Q3 2026 Sales Report';
    summary.Subject = 'Quarterly results by sales department';
    summary.Author = 'E-iceblue';
    summary.Keywords = 'sales, report, Excel';
    summary.Comments = 'Summary filled in after the export';
    summary.Category = 'Sales Report';

    // Set the entries carried by the extended properties
    summary.Company = 'E-iceblue';
    summary.Manager = 'Sales Manager';

    // Set the document dates; a Date object is required here
    summary.CreatedTime = new Date(2026, 8, 1);
    summary.LastSaveTime = new Date(2026, 8, 20);

    // Save the workbook
    const outputFileName = 'SetSummaryProperties.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result file back out of the VFS and download it
    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 workbook summary properties</h1>
      <button onClick={setSummaryProperties}>Start</button>
    </div>
  );
}

export default App;

The effect of setting the summary properties of a workbook:

Set the workbook summary properties


Add custom properties

Custom properties carry the business fields the summary properties have no room for, such as an export batch, a contact phone number, a revision number or an approval date. Unlike the summary properties they have no fixed set of entries: the caller decides the name and the type, and Excel accepts text, integer, decimal, boolean and date-and-time values. The steps are:

  1. Load the workbook and take the custom property collection from workbook.CustomDocumentProperties.
  2. Call Add to append a property; the name and the value can be written as a named object such as { strName, boolValue }, or passed directly as two arguments.
  3. Pick the member that matches the type of the value: intValue for an integer, dblValue for a decimal.
  4. Pass a Date object as dtValue for a date-and-time value.
  5. Save the workbook.

Here is a complete code example showing how to add custom properties to a workbook in React:

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

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

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // Add a boolean property; _MarkAsFinal marks the document as final
    workbook.CustomDocumentProperties.Add({ strName: '_MarkAsFinal', boolValue: true });

    // Add a text property; a name and a value can also be passed directly
    workbook.CustomDocumentProperties.Add('The Editor', 'E-iceblue');

    // Add an integer property
    workbook.CustomDocumentProperties.Add({ strName: 'Phone number', intValue: 81705109 });

    // Add a decimal property
    workbook.CustomDocumentProperties.Add({ strName: 'Revision number', dblValue: 7.12 });

    // Add a date and time property
    workbook.CustomDocumentProperties.Add({ strName: 'Revision date', dtValue: new Date(2026, 8, 1) });

    // Save the workbook
    const outputFileName = 'AddCustomProperties.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result file back out of the VFS and download it
    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 custom properties</h1>
      <button onClick={addCustomProperties}>Start</button>
    </div>
  );
}

export default App;

The effect of adding custom properties:

Add custom properties


Update the value of a custom property

The value of a business field changes with the way it is counted: an export record count, say, has to be rewritten to a new figure once the data is topped up. Custom properties offer no member that assigns a value directly, but Add is keyed on the name — calling it again for a name that already exists does not append a duplicate entry, it replaces the value of the existing one. The steps are:

  1. Load the workbook and take the custom property collection from workbook.CustomDocumentProperties.
  2. Call Add again, passing the same name as the existing entry together with the new value.
  3. Save the workbook.

Here is a complete code example showing how to update a custom property of a workbook in React:

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

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

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // Rewrite the exported record count; adding an existing name overwrites it
    workbook.CustomDocumentProperties.Add({ strName: 'ExportedRecords', intValue: 256 });

    // Save the workbook
    const outputFileName = 'UpdateCustomProperties.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result file back out of the VFS and download it
    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>Update the value of a custom property</h1>
      <button onClick={updateCustomProperties}>Start</button>
    </div>
  );
}

export default App;

The effect of updating the value of a custom property:

Update the value of a custom property


FAQ

Why does assigning to CreatedTime throw "Assert failed: Value is not a Date"

Cause: CreatedTime and LastSaveTime take a JavaScript Date object and nothing else. A date string such as '2026-09-01', a timestamp number, or a hand-made object that only carries a toISOString() method all fail the type check and throw Assert failed: Value is not a Date.

Solution: build the Date with new Date(...) first, then assign it:

// 1 September 2026; months count from 0, so 8 means September
workbook.DocumentProperties.CreatedTime = new Date(2026, 8, 1);

Why does assigning to the Value of a custom property throw ArgumentNull_Generic

Cause: the Value property only reads. Assigning to it throws ArgumentNull_Generic Arg_ParamName_Name, value, and the original value does not change. Updating an existing property has to go through Add.

Solution: overwrite the original value with a same-name Add:

workbook.CustomDocumentProperties.Add({ strName: 'ExportedRecords', intValue: 256 });

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.

The real dividers in a spreadsheet are not blank cells but borders: financial reports use lines of different weights to separate the header row from the totals, while data handed to a downstream system has to go out with the frames stripped off. Doing that by hand — selecting each block and opening Format Cells — is slow and hard to repeat. Spire.XLS for JavaScript does the same work in the browser on top of WebAssembly, using a virtual file system (VFS) for input and output files, so no back-end service is needed.

This article covers four feature points:

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 initialised.


Add a border to a selected cell or a cell range

Given a table with no lines at all, the most direct approach is to put the frame back with BorderAround and BorderInside: the first one draws the outline, the second one the grid lines inside the range. Together they frame a whole block of data in one go, and they can also single out one cell — the header cell, say — so that it stands out from a sheet full of identical lines.

The steps are:

  1. Take the cell range you want to frame with Range.get, by address
  2. Call BorderAround to draw the outline, with the line style taken from the LineStyleType enumeration
  3. Call BorderInside to add the separators between the cells inside the range
  4. Take the header cell on its own and call BorderAround again on it, this time with a medium line

The line style is passed in object notation, as { borderLine }.

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

    // Take the B2:E6 range, frame it with thin lines and add thin inner lines
    const dataRange = sheet.Range.get('B2:E6');
    dataRange.BorderAround({ borderLine: xlsModule.LineStyleType.Thin });
    dataRange.BorderInside({ borderLine: xlsModule.LineStyleType.Thin });

    // Give the B2 header cell a medium border on all four sides
    sheet.Range.get('B2').BorderAround({ borderLine: xlsModule.LineStyleType.Medium });

    // Save the result file
    const outputFileName = 'AddBorderToCells.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Dispose of the workbook object to free resources
    workbook.Dispose();

    // Read the result file back out of the VFS and download it
    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 or Remove Cell Borders</h1>
      <button onClick={addBorderToCells}>Add a border to a cell or range</button>
    </div>
  );
}

export default App;

The effect of adding a border to the data range and to the header cell:

Add a border to a selected cell or a cell range


Add a border to the range that holds the data

The previous section hard-codes the address B2:E6, which has to be edited as soon as rows or columns are added to the table. The AllocatedRange property returns the allocated range of a worksheet — the rectangle that actually holds the data — so the address maintains itself, and the outline can be given a different style from the header frame to set the table apart from the text around it.

The steps are:

  1. Get the range that holds the data through AllocatedRange, with no address to maintain
  2. Call BorderAround with a medium dashed line, so the outline contrasts with the solid frames used elsewhere
  3. Call BorderInside to add thin lines inside the range, keeping the rows separated
function App() {
  const addBorderToDataRange = async () => {
    const xlsModule = window.wasmModule?.spirexls;
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'CellBorders.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // AllocatedRange returns the range that holds the data, so no address is needed
    const dataRange = sheet.AllocatedRange;

    // Give the outline a medium dashed border and the inside thin lines
    dataRange.BorderAround({ borderLine: xlsModule.LineStyleType.MediumDashed });
    dataRange.BorderInside({ borderLine: xlsModule.LineStyleType.Thin });

    const outputFileName = 'AddBorderToDataRange.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
    workbook.Dispose();

    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 or Remove Cell Borders</h1>
      <button onClick={addBorderToDataRange}>Add a border to the data range</button>
    </div>
  );
}

export default App;

The effect of adding a dashed outline and thin inner lines to the data range:

Add a border to the range that holds the data


Add left, top, right, bottom and diagonal borders to a cell

BorderAround gives all four edges the same style, which is not enough when the requirement is "a thick red line on the left and a double line along the bottom". Handling the edges one at a time solves it: Borders.get takes a single edge by its BordersLineType member, and its LineStyle and Color can then be set independently, so every edge can have its own line style and colour. The two diagonal directions are available the same way.

The steps are:

  1. Take the cell to style with Range.get
  2. Take the left, top, right and bottom edges through Borders.get(BordersLineType.EdgeLeft) and its siblings
  3. Set LineStyle on each edge, using a thick line, a dotted line, a slanted dash-dot line and a double line
  4. Set Color on each edge, using red, brown, dark grey and orange red
  5. Take another cell, fetch its diagonal-down edge with BordersLineType.DiagonalDown and set its line style
function App() {
  const addEdgeBorders = async () => {
    const xlsModule = window.wasmModule?.spirexls;
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'CellBorders.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // Give each of the four edges of B4 its own line style and colour
    const cell = sheet.Range.get('B4');
    const edgeSpecs = [
      ['EdgeLeft', xlsModule.LineStyleType.Thick, xlsModule.Color.get_Red()],
      ['EdgeTop', xlsModule.LineStyleType.Dotted, xlsModule.Color.get_Brown()],
      ['EdgeRight', xlsModule.LineStyleType.SlantedDashDot, xlsModule.Color.get_DarkGray()],
      ['EdgeBottom', xlsModule.LineStyleType.Double, xlsModule.Color.get_OrangeRed()],
    ];
    for (const [edge, lineStyle, color] of edgeSpecs) {
      const border = cell.Borders.get(xlsModule.BordersLineType[edge]);
      border.LineStyle = lineStyle;
      border.Color = color;
    }

    // Add a diagonal-down border to E6
    const diagonal = sheet.Range.get('E6').Borders.get(xlsModule.BordersLineType.DiagonalDown);
    diagonal.LineStyle = xlsModule.LineStyleType.Thin;

    const outputFileName = 'AddEdgeBorders.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
    workbook.Dispose();

    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 or Remove Cell Borders</h1>
      <button onClick={addEdgeBorders}>Set the four edges and a diagonal</button>
    </div>
  );
}

export default App;

The effect of styling the four edges of a cell and adding a diagonal line:

Add left, top, right, bottom and diagonal borders to a cell


Remove the borders of a cell or a cell range

When an internal report goes out to a customer, or its data is fed to a downstream system, borders are often just noise: they make the data look like a finished report, and a parser that walks the sheet region by region can pick them up as content. Removing them is as simple as setting them — assign LineStyleType.None to the range's Borders.LineStyle and every line inside it goes at once.

The steps are:

  1. Take the worksheet that holds the framed report
  2. Take the cell range to clean up with Range.get
  3. Set the range's Borders.LineStyle to LineStyleType.None, clearing every border inside it
function App() {
  const removeBorders = async () => {
    const xlsModule = window.wasmModule?.spirexls;
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'CellBorders.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

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

    // The second worksheet holds the same report, already framed with borders
    const sheet = workbook.Worksheets.get(1);

    // Set every border in the B2:E6 range to None
    sheet.Range.get('B2:E6').Borders.LineStyle = xlsModule.LineStyleType.None;

    const outputFileName = 'RemoveBorders.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
    workbook.Dispose();

    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 or Remove Cell Borders</h1>
      <button onClick={removeBorders}>Remove cell borders</button>
    </div>
  );
}

export default App;

The effect of clearing every border inside the range:

Remove the borders of a cell or a cell range


FAQ

Setting a top edge on a whole range puts a line above every row

Cause: Borders.get(BordersLineType.EdgeTop) returns the top edge of every cell in the range, not the outline of the range itself. Used on B2:E6 as it stands, every row inside the range gets a top edge of its own, so four extra lines appear in the middle of the table.

Solution: narrow the range to a single row or column, and the edge that comes back sits on the outline. Take the top from the first row, the bottom from the last row, the left from the first column and the right from the last column:

// Top: the first row only
sheet.Range.get('B2:E2').Borders.get(xlsModule.BordersLineType.EdgeTop).LineStyle = xlsModule.LineStyleType.Thin;
// Bottom: the last row only
sheet.Range.get('B6:E6').Borders.get(xlsModule.BordersLineType.EdgeBottom).LineStyle = xlsModule.LineStyleType.Thin;
// Left: the first column only
sheet.Range.get('B2:B6').Borders.get(xlsModule.BordersLineType.EdgeLeft).LineStyle = xlsModule.LineStyleType.Thin;
// Right: the last column only
sheet.Range.get('E2:E6').Borders.get(xlsModule.BordersLineType.EdgeRight).LineStyle = xlsModule.LineStyleType.Thin;

BorderInside throws when it is called on a single cell

Cause: BorderInside means "the separators inside the range", and a single cell has no inside, so passing one throws This method doesn't support for single cell. No result file is written either.

Solution: use BorderAround to frame a single cell, or set its edges one at a time as in the previous section:

sheet.Range.get('B2').BorderAround({ borderLine: xlsModule.LineStyleType.Thin });

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.

A product rename, a wording change, a company name typed wrong in a template — edits like these are awkward once they land in a PDF. When the source document is not at hand, the only options are to export the whole page as an image and lay text over it, or to delete the original text in a reader and type a replacement in by hand. Contracts and manuals make this worse: the same name can be scattered across body text, bullet lists and closing notes, and fixing each one by hand is slow and easy to miss.

Spire.PDF for JavaScript loads, edits and saves PDF documents in the browser on WebAssembly, so a rename is only a matter of locating the text and writing it back, with files read and written through a virtual file system (VFS) and no backend involved. The four sections below cover replacing the first match, replacing every match (together with three switches: recoloring, match mode and replacement area), covering the original word with a background color, and drawing a rectangle over the original before redrawing the new word. In the sample the old and new names are the same length, so the layout does not shift after replacement.

This article covers four core features:

For installation and project configuration, see Integrate Spire.PDF for JavaScript into a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Replace the First Match

Spire.PDF for JavaScript provides the PdfTextReplacer class for turning text on a page into another piece of text. It is built per page, and ReplaceText replaces only the first match it finds, returning the number of replacements actually made (1 here); any remaining occurrences of the same name on the page are left as they are.

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

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'ProductOverview.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Take the first page; the replacement is limited to this page
    let page = doc.Pages.get_Item(0);

    // Create the text replacer and replace only the first match
    const replacer = new pdfModule.PdfTextReplacer(page);
    replacer.ReplaceText('Stellar Drive', 'Nova Drive');

    const outputFileName = 'ReplaceFirstMatch.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 First Match</h1>
      <button onClick={replaceFirstMatch}>
        Replace
      </button>
    </div>
  );
}

export default App;

Only the first match is replaced:

Only the first match is replaced — the lead already shows the new name, the list and the closing note still show the old one


Replace All Matches

Spire.PDF for JavaScript also provides PdfTextReplacer.ReplaceAllText(), which swaps every matching piece of text on the page in one go. It takes the same arguments as ReplaceText and likewise returns the number of replacements. It also has a three-argument overload, ReplaceAllText(old, new, color), which recolors the text as it replaces it.

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

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'ProductOverview.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    let page = doc.Pages.get_Item(0);

    // The third argument is a color, recoloring the new word as it is written
    const replacer = new pdfModule.PdfTextReplacer(page);
    replacer.ReplaceAllText('Stellar Drive', 'Nova Drive', pdfModule.Color.get_Red());

    const outputFileName = 'ReplaceAllMatches.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 All Matches</h1>
      <button onClick={replaceAllMatches}>
        Replace
      </button>
    </div>
  );
}

export default App;

All old name are replaced, and the new name is recolored red at the same time:

All four occurrences of the old name replaced and the new name recolored red

Beyond recoloring, PdfTextReplacer.Options has two more switches. ReplaceType decides what counts as a match: by default only an identical string is accepted, and it can be switched to IgnoreCase, WholeWord or Regex. SetReplacementArea limits the replacement to a rectangle, which PdfTextFinder can work out first:

const replacer = new pdfModule.PdfTextReplacer(page);
// Ignore case
replacer.Options.ReplaceType = pdfModule.ReplaceActionType.IgnoreCase;

// Limit the replacement to a rectangle
const finds = new pdfModule.PdfTextFinder(page).Find('Stellar Drive');
replacer.Options.SetReplacementArea(finds.get(0).Bounds[0]);

// Call it once the options are set
const count = replacer.ReplaceAllText('Stellar Drive', 'Nova Drive');

Replace by Covering

Spire.PDF for JavaScript also provides PdfTextFragment.ApplyRecoverString(), which covers a located piece of text in place: it lays a background color over the original and then writes the new text in the same position. It spares you from working out coordinates and redrawing by hand. The price is that only the background color can be chosen: the new text is drawn with the font resolved from the VFS and keeps the original foreground color, and with a white background the seam is invisible. When the font and the color need to change as well, see the redrawing approach in the next section.

function App() {
  const replaceByCovering = 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 fonts used by the document into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'ProductOverview.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    let page = doc.Pages.get_Item(0);

    // Locate the target text with a finder first
    const finder = new pdfModule.PdfTextFinder(page);
    const finds = finder.Find('Stellar Drive');

    // Cover each hit in turn: the second argument is the background color,
    // and the third one, true, writes the new text as Unicode
    for (let i = 0; i < finds.length; i++) {
      finds.get(i).ApplyRecoverString('Nova Drive', pdfModule.Color.get_White(), true);
    }

    const outputFileName = 'RecoverReplace.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 by Covering</h1>
      <button onClick={replaceByCovering}>
        Replace
      </button>
    </div>
  );
}

export default App;

A document where the original text is covered with a white background and the new word is written in its place:

The original text covered with a white background and the new word written in its place


Draw a Rectangle to Cover the Original and Redraw the Text

The first three sections all work on the text layer. This one takes a different tack: use PdfTextFinder to get the position of the keyword, draw a white rectangle to cover the original text, and then write the new text inside that rectangle. Compared with ApplyRecoverString, the extra freedom is that you choose the font, size and color yourself.

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

    // Load the PDF file to be processed into the VFS
    const inputFileName = 'ProductOverview.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    let page = doc.Pages.get_Item(0);

    // Locate the target text with a finder first
    const finder = new pdfModule.PdfTextFinder(page);
    const finds = finder.Find('Stellar Drive');

    // The white rectangle that covers the original, plus the new word's font and color
    const white = pdfModule.PdfBrushes.get_White();
    const FONT_SIZE = 9;
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL.TTF', size: FONT_SIZE });
    const brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkBlue() }) });

    // Turn LineLimit off so the text is not clipped to the box
    const format = new pdfModule.PdfStringFormat();
    format.LineLimit = false;
    // Set the alignment of the text
    format.Alignment = pdfModule.PdfTextAlignment.Center;
    format.LineAlignment = pdfModule.PdfVerticalAlignment.Middle;

    for (let i = 0; i < finds.length; i++) {
      const rec = finds.get(i).Bounds[0];

      // Cover the original text with a white rectangle first
      page.Canvas.DrawRectangle({ brush: white, rectangle: rec });

      // Write the new word straight into the hit box
      page.Canvas.DrawString({ s: 'Nova Drive', font: font, brush: brush, layoutRectangle: rec, format: format });
    }

    const outputFileName = 'DrawRectangleAndText.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>Draw a Rectangle to Cover the Original and Redraw the Text</h1>
      <button onClick={replaceByDrawing}>
        Replace
      </button>
    </div>
  );
}

export default App;

A document where a white rectangle covers the original text and the new word is written directly inside the hit box:

A white rectangle covering the original text with the new word written directly inside the hit box


FAQ

The old name can still be found and copied after replacement

Cause: ApplyRecoverString is a visual-layer operation — it lays a background color over the original text and draws the new word on top, while the original text object stays in the text layer, so searching or copying in a reader still returns the old name. Measured on the covering replacement, the occurrence count of the old word is unchanged while the new word gains four hits.

Fix: When a real replacement at the content level is needed, switch to PdfTextReplacer; in its output the old word disappears from the text layer and the new word enters it. Measured, the four occurrences of the old word drop to 0 and the new word rises from 1 to 5:

// Content-level replacement: the old text no longer remains
const replacer = new pdfModule.PdfTextReplacer(page);
replacer.ReplaceAllText('Stellar Drive', 'Nova Drive');

Following the official sample's white rectangle plus DrawString redraw, the white background appears but the new text does not

Cause: The official sample follows the same approach as the fourth section here — locate the position with PdfTextFinder, draw a white rectangle to cover the original text, then paint the new text with Canvas.DrawString. The white rectangle is fine, but the redraw step has a trap:

  • The rectangle overload of DrawString is governed by PdfStringFormat.LineLimit, which defaults to true and clips the text to the box; the hit box hugs the glyphs tightly, so if its height is 11 points (exactly the font size) it cannot hold a full line height, and the whole line is clipped away — the new text appears neither on the page nor in the text layer.

Fix: Pass a PdfStringFormat with LineLimit turned off and the hit box can be used as the layout box as it is (which is what the fourth section does).

const format = new pdfModule.PdfStringFormat();
format.LineLimit = false;

page.Canvas.DrawString({ s: 'Nova Drive', font: font, brush: brush, layoutRectangle: rec, format: format });

Get a Free License

If you want to remove the evaluation message from the result document, or get past the feature limits, contact sales for a 30-day temporary license.

page 14