A PDF's version number decides which features the document may use, and whether older readers, print systems, and archival platforms can open it at all. Documents collected from many sources come with mixed versions: some produced by new tools and declaring 1.7, others from systems that have not been updated in years and still sit at 1.4. To deliver a uniform format for a target environment, the version number has to be rewritten — and desktop software only lets you do that by hand, one file at a time.

This article uses Spire.PDF for JavaScript to change a PDF document's version number. It runs on WebAssembly to load, modify, and save PDFs directly in the browser, working through a virtual file system (VFS) with no backend required.

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


Change the PDF Version

Assign a PdfVersion value to doc.FileInfo.Version to rewrite the version number; once saved, the file header is written as the matching %PDF-x.y. Set it to Version1_4 and a 1.7 document drops to 1.4, ready for readers or print systems that only accept the older specification.

function App() {
  const changePdfVersion = 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 PDF to be processed into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

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

    // Set the version number to the target value, here downgrading to 1.4
    doc.FileInfo.Version = pdfModule.PdfVersion.Version1_4;

    const outputFileName = 'Version-1.4.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>Change PDF Version</h1>
      <button onClick={changePdfVersion}>
        Change
      </button>
    </div>
  );
}

export default App;

A document saved with the version set to 1.4 — its file header now reads %PDF-1.4

A document saved with the version set to 1.4 — its file header now reads %PDF-1.4


FAQ

Why does the version stay the same after I set it and reopen the file

Reason: FileInfo.Version changes a property of the in-memory document object, and only saving writes it into the file. Close the document right after setting the version, or open the original input file again, and you will of course still see the old version number.

Solution: After assigning the value you must call SaveToFile to write a new output file, then open that result to check it:

// Set the version number first
doc.FileInfo.Version = pdfModule.PdfVersion.Version1_4;

// Then save — only now is the version number written to the file
doc.SaveToFile('Version-1.4.pdf');

Which version number should I set

Reason: It depends on what the recipient supports. Older print systems and archival platforms often require 1.4 or lower, while newer tools mostly handle 1.7. FileInfo.Version takes a PdfVersion enum, where Version1_0 through Version1_7 map to PDF 1.0 through PDF 1.7 in order.

Solution: Set the highest version the recipient accepts, assigning the enum directly:

// Deliver to readers or print systems that only accept the older specification
doc.FileInfo.Version = pdfModule.PdfVersion.Version1_4;

// The target environment is newer — keep or raise it to 1.7
doc.FileInfo.Version = pdfModule.PdfVersion.Version1_7;

Why does the document fail to open or render incorrectly after downgrading

Reason: FileInfo.Version only changes the declaration; it does not clean up objects in the document that go beyond that version's specification. If the source uses features introduced in a later version — such as cross-reference streams, object streams, or compressed transparency from PDF 1.5 onward — an older reader parsing it as a lower version will fail.

Solution: Before changing the declaration, confirm the source file does not rely on those features. If it is clean, changing the version number is enough; otherwise, use a source file that avoids the newer features, or first rebuild the document in a way that pushes its objects back to the older specification (re-export, or print to PDF) and then change the version number.


Get a Free License

If you want to remove the evaluation message from the result documents or get rid of feature limitations, please contact sales to obtain a free 30-day temporary license.

Contracts, bids, and other multi-page documents get a cross-page seal before they go out, so that every page carries a fragment of the seal — if the other party swaps or reorders pages, the fragments no longer line up. With paper you stamp the whole stack at once; an electronic PDF has no equivalent gesture: paste the seal image on whole and every page shows a complete seal, which serves no cross-page purpose.

This article shows how to add a cross-page seal to a multi-page contract with Spire.PDF for JavaScript. Built on WebAssembly, it loads, modifies, and saves PDF documents directly in the browser, so the whole process stays local and files are read and written through a virtual file system (VFS) with no backend service required.

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following example assumes Spire.PDF is installed and the WebAssembly module has been initialized.


Add a Cross-Page Seal

A cross-page seal cuts one seal into as many pieces as there are pages, leaving one piece on each page, so that flipping through the pages rebuilds the complete seal. The cutting is carried by a PdfTemplate: the template's bounds are the clipping area, so shifting the seal one strip width further left on each successive page makes exactly that page's strip show through, and Canvas.DrawTemplate drops it at the right edge of the page.

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

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

    // Load the document to be sealed and the seal image into the VFS
    const inputFileName = 'Number.pdf';
    const sealFileName = 'Seal.png';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(sealFileName, '', `${process.env.PUBLIC_URL}/data/`);

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

    // Load the seal image; its width and height are in pixels, treated as points (1 px = 1 pt) when laid out
    const seal = pdfModule.PdfImage.FromFile(sealFileName);

    // Overall scale of the seal, and the scaled seal width and height
    const scale = 0.55;
    const sealWidth = seal.Width * scale;
    const sealHeight = seal.Height * scale;

    // Split the seal evenly by page count, one strip per page
    const pageCount = doc.Pages.Count;
    const stripWidth = sealWidth / pageCount;

    // Draw the i-th strip of the seal at the right edge of each page
    for (let i = 0; i < pageCount; i++) {
      const page = doc.Pages.get_Item(i);

      // The template bounds are the clipping area: one strip wide and as tall as the seal
      const template = new pdfModule.PdfTemplate({ width: stripWidth, height: sealHeight });
      template.Graphics.ScaleTransform(scale, scale);

      // Shift the seal one more strip to the left on every page, so the next strip shows through
      template.Graphics.DrawImage({ image: seal, x: -(i * seal.Width) / pageCount, y: 0 });

      // Every page uses the same spot: 24 points in from the right edge, vertically centered
      const x = page.Canvas.ClientSize.Width - 24 - stripWidth;
      const y = (page.Canvas.ClientSize.Height - sealHeight) / 2;
      page.Canvas.DrawTemplate({ template: template, location: new pdfModule.PointF(x, y) });
    }

    // Save to the VFS
    const outputFileName = 'Cross-Page-Seal.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>Add a Cross-Page Seal</h1>
      <button onClick={addCrossPageSeal}>
        Add Seal
      </button>
    </div>
  );
}

export default App;

The contract after the cross-page seal is applied: each page carries one fragment of the seal at its right edge, and turning to the last page shows the right half of the seal:

The contract after the cross-page seal is applied; each page carries one fragment of the seal at its right edge


FAQ

Why does each page show only a narrow slice of the seal

Reason: A cross-page seal is by definition cut evenly across the pages, so each page carries only the strip seal width / page count. The more pages, the narrower the fragment on each page — that is by design: a narrow fragment at a fixed position is what makes a single swapped page impossible to line back up.

Solution: The page count is decided by the document, so to make the seal more prominent the only lever is to enlarge the whole seal, and the fragments grow with it:

// Raise 0.55, say to 0.8: the whole seal and every page's fragment grow together
const scale = 0.8;
const sealWidth = seal.Width * scale;

The seal is so large it covers the body text

Reason: PdfImage.Width and Height report pixel values, and without scaling they are laid out at 1 pixel = 1 point. A 400×400-pixel seal takes up 400 points of width, while an A4 page is only 595 points wide, so it will cover the body text.

Solution: Scale it down proportionally inside the template with ScaleTransform, and convert the seal width, seal height, and strip width by the same factor — all three must come from one source, or the strips will not line up with the placement:

const scale = 0.55;
const sealWidth = seal.Width * scale;
const sealHeight = seal.Height * scale;
const stripWidth = sealWidth / pageCount;

Get a Free License

If you wish to remove the evaluation message from the result document or remove feature limitations, please contact sales to obtain a temporary license valid for 30 days.

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.

page 14