Migration from SheetJS
The xlsx-format API is intentionally close to SheetJS. Most code starts with three syntax changes, followed by a few behavior checks for security, supported formats, and memory use.
The Three Changes
1. read() and write() return Promises
ZIP compression and decompression use Web Streams, so both functions return a Promise. XML parsing, worksheet traversal, and serialization still run synchronously, and read() / write() accept and return complete in-memory values. Add await to every call, and use a worker when a large workbook must not share the main event loop.
2. Named imports replace the namespace
Instead of accessing everything through XLSX.*, import each function by name.
3. Utility names are camelCase
sheet_to_json becomes sheetToJson, sheet_to_csv becomes sheetToCsv, and so on.
Check behavior differences
After the syntax changes, check these material differences before switching a production path:
- Export safety defaults: CSV formula-like text is escaped and unsafe HTML hyperlink targets are removed by default. Keep these defaults for untrusted output; see Security Considerations for the explicit opt-outs.
- Encrypted workbooks: Password-protected workbooks are unsupported.
read()andwrite()reject a non-emptypasswordoption. - Supported formats: The supported file formats are XLSX, XLSM, CSV, TSV, and HTML. Legacy XLS, XLSB, ODS, and other formats from the broader SheetJS surface are outside this package's scope. See the feature matrix.
- Output and memory:
read()parses a complete in-memory input, andwrite()returns a complete in-memory output. Web Streams cover ZIP compression and decompression; they do not make XML parsing or worksheet loops incremental.
Side-by-Side
- import XLSX from "xlsx";
+ import { read, write, sheetToJson, sheetToCsv } from "xlsx-format";
- const wb = XLSX.read(buffer);
+ const wb = await read(buffer);
- const rows = XLSX.utils.sheet_to_json(ws);
+ const rows = sheetToJson(ws);
- const buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" });
+ const buf = await write(wb, { type: "buffer" });
- const csv = XLSX.utils.sheet_to_csv(ws);
+ const csv = sheetToCsv(ws);Function Name Mapping
| SheetJS | xlsx-format |
|---|---|
XLSX.read() | read() |
XLSX.readFile(path) | read(await fs.readFile(path)) |
XLSX.write() | write() |
XLSX.writeFile(wb, path) | await fs.writeFile(path, await write(wb)) |
XLSX.utils.sheet_to_json() | sheetToJson() |
XLSX.utils.json_to_sheet() | jsonToSheet() |
XLSX.utils.sheet_to_csv() | sheetToCsv() |
XLSX.utils.sheet_to_html() | sheetToHtml() |
XLSX.utils.aoa_to_sheet() | arrayToSheet() |
XLSX.utils.sheet_to_formulae() | sheetToFormulae() |
XLSX.utils.decode_cell() | decodeCell() |
XLSX.utils.encode_cell() | encodeCell() |
XLSX.utils.decode_range() | decodeRange() |
XLSX.utils.encode_range() | encodeRange() |
XLSX.utils.book_new() | createWorkbook() |
XLSX.utils.book_append_sheet() | appendSheet() |
File I/O
xlsx-format does not include readFile / writeFile -- it stays platform-agnostic by keeping node:fs out of the bundle. Use Node's fs module directly:
- import XLSX from "xlsx";
+ import { readFile, writeFile } from "node:fs/promises";
+ import { read, write } from "xlsx-format";
- const wb = XLSX.readFile("input.xlsx");
+ const wb = await read(await readFile("input.xlsx"));
- XLSX.writeFile(wb, "output.xlsx");
+ await writeFile("output.xlsx", await write(wb));For CSV or HTML files, read as a UTF-8 string:
const wb = await read(await readFile("data.csv", "utf-8"), { type: "string" });
const tsv = await read(await readFile("data.tsv", "utf-8"), { type: "string", FS: "\t" });Read and Write Result Types
Metadata-only reads now expose their actual runtime shapes. bookSheets: true returns SheetNames, bookProps: true returns Props and Custprops, and combining both options returns both sets of metadata. These results do not include Sheets. Code that stores the flags in a broadly typed ReadOptions object receives a safe union and must narrow the result before accessing worksheet data.
write() also reports the output selected by bookType and type:
| Request | Result |
|---|---|
XLSX or XLSM, default / array / buffer / string | Uint8Array |
Any format with type: "base64" | string |
CSV, TSV, or HTML with default / type: "string" | string |
CSV, TSV, or HTML with type: "array" / type: "buffer" | Uint8Array |
In Node.js, type: "buffer" returns a Buffer, which is a Uint8Array subtype. In browsers it falls back to a plain Uint8Array, so portable code should use the declared Uint8Array API. The XLSX type: "string" compatibility input continues to return ZIP bytes; use base64 when a string representation of an XLSX file is required.
Compatibility Options and Macros
Some SheetJS-compatible option names are retained so shared configuration objects do not fail type checking, but affirmative requests now fail explicitly instead of being ignored:
read()rejectsbookFiles,bookVBA,bookDeps, andxlfnwhen set totrue.write()rejectsbookVBA: trueand non-emptythemeXLSXvalues.nodim: trueis supported. It ignores the stored worksheet dimension and recalculates an A1-anchored range from parsed cells.
False, empty, and omitted compatibility values keep their existing behavior. Unsupported requests throw XlsxError with code UNSUPPORTED.
xlsx-format can read cell data from XLSM containers and can write a macro-enabled container with bookType: "xlsm", but it does not read, preserve, or write VBA projects. Writing a workbook with a non-empty vbaraw payload throws UNSUPPORTED to prevent silent macro loss. Do not use a read/write round trip when macros must survive unchanged.
Cell Objects Stay the Same
Cell objects keep the same shape. { t: "n", v: 42, w: "42" } works exactly as before. No data migration needed.
Replacing ExcelJS for Styled Exports
If you only keep ExcelJS for polished report exports, xlsx-format can cover that job without adopting the ExcelJS workbook class model. Keep your sheet-shaped data and add styles through cell.s or helper functions.
import { arrayToSheet, createWorkbook, styleRange, mergeCells, freezePanes, write } from "xlsx-format";
const sheet = arrayToSheet([
["Northstar Solar PPA - Q2 Report", null, null],
["Month", "Expected MWh", "Settlement"],
["Apr 2026", 12400, -18350],
]);
sheet["A1"].s = {
font: { bold: true, color: { argb: "FFFFFFFF" } },
fill: { patternType: "solid", fgColor: { argb: "FF1F4E79" } },
};
styleRange(sheet, "A2:C2", {
font: { bold: true, color: { argb: "FFFFFFFF" } },
fill: { patternType: "solid", fgColor: { argb: "FF2E75B6" } },
alignment: { horizontal: "center", wrapText: true },
});
mergeCells(sheet, "A1:C1");
freezePanes(sheet, { ySplit: 2 });
const data = await write(createWorkbook(sheet, "Overview"), {
type: "array",
cellStyles: true,
});This is intentionally not an ExcelJS compatibility layer. It is a smaller style-writing model for browser-friendly XLSX reports: fonts, fills, borders, alignment, number formats, row heights, column widths, merged cells, frozen panes, and multiple sheets.
What's Different Under the Hood
- Promise API -- ZIP compression and decompression use Web Streams, while XML parsing and worksheet traversal operate synchronously on complete in-memory values
- Tree-shaking -- named exports let your bundler drop unused code
- No legacy formats -- xlsx-format focuses on XLSX, macro-free XLSM containers, CSV, TSV, and HTML
- No runtime dependencies -- the published package installs no third-party runtime packages
- Platform-agnostic -- no
node:fsimports; the library works in Node.js, browsers, and edge runtimes without polyfills