Live demo · Website · English · Português (BR)
Runs in Node and the browser — it only needs Uint8Array / DataView. It ships
as ~3.8 KB min+gzip with zero runtime dependencies, dual ESM/CJS, fully typed,
and published to npm with provenance.
npm install xls-reader
# or: pnpm add xls-reader / yarn add xls-reader- Why this exists
- Usage
- Convert an .xls to JSON
- Command line
- API
- Comparison
- Supported
- Limitations
- FAQ
- Contributing
Modern readers like ExcelJS only handle the
newer .xlsx (OOXML) format. The de-facto .xls reader,
SheetJS, no longer publishes to the npm registry — its
last npm release (0.18.5) is frozen with known advisories, and fixed versions
ship only from its own CDN. Installing from a CDN means npm audit and
Dependabot can't see the package, so you lose automated vulnerability alerts.
xls-reader is a small, focused, npm-published alternative for the common
case: read the cells out of a legacy .xls. No dependencies, MIT-licensed,
covered by the same supply-chain tooling as the rest of your tree.
import { readFile } from "node:fs/promises";
import { readXls } from "xls-reader";
const workbook = readXls(await readFile("posicao.xls"));
for (const sheet of workbook.sheets) {
console.log(sheet.name);
for (const row of sheet.rows) {
console.log(row); // e.g. ["BANCO DAYCOVAL S/A", "CDB", "CDI", 1.1, Date(2024-04-02), 1000, ...]
}
}Single-sheet shortcut:
import { readFirstSheet } from "xls-reader";
const sheet = readFirstSheet(bytes);In the browser (e.g. from a file <input>):
import { readXls } from "xls-reader";
input.addEventListener("change", async () => {
const file = input.files?.[0];
if (!file) return;
const workbook = readXls(await file.arrayBuffer());
console.table(workbook.sheets[0]?.rows);
});sheetToObjects uses the first row as the keys and returns one object per data
row:
import { readFirstSheet, sheetToObjects } from "xls-reader";
const sheet = readFirstSheet(bytes);
const json = sheet ? sheetToObjects(sheet) : [];
// [{ Emitente: "BANCO X S/A", Taxa: 1.1, Data: 2024-04-02T00:00:00.000Z }, ...]Blank-header columns are skipped and short rows are padded with null. Pass
{ headerRow } when the header isn't the first row (e.g. a title row above it).
Handling non-.xls input:
import { readXls, XlsError } from "xls-reader";
try {
const workbook = readXls(bytes);
} catch (err) {
if (err instanceof XlsError) {
// Not a BIFF .xls — e.g. it's really an .xlsx, CSV, or HTML mislabeled as .xls
console.error(err.message);
} else {
throw err;
}
}No code needed for a quick look or a shell pipeline — the package ships an
xls-reader bin that prints a workbook's cells as JSON to stdout:
npx xls-reader report.xls # every sheet, pretty-printed
npx xls-reader report.xls --objects # rows keyed by the header row
npx xls-reader report.xls --sheet 0 --compact > sheet0.json| Flag | Effect |
|---|---|
--objects |
Each row as an object keyed by the header row |
--sheet <name|n> |
Only the sheet with this name or 0-based index |
--visible-only |
Skip hidden and very-hidden sheets |
--compact |
Single-line JSON (default is pretty-printed) |
JSON goes to stdout; errors go to stderr with a non-zero exit code.
Parses a whole workbook. Throws XlsError if the bytes aren't a BIFF .xls.
The first worksheet, for the single-sheet case.
Turns a sheet's rows into objects keyed by a header row (the first by default).
Blank-header columns are skipped and short rows are padded with null.
The raw serial-number → Date (UTC) conversion, if you need it directly.
type Cell = string | number | boolean | Date | CellError | null;
// An Excel error value, e.g. new CellError("#DIV/0!"). `.code` is the error text.
class CellError {
readonly code: "#NULL!" | "#DIV/0!" | "#VALUE!" | "#REF!" | "#NAME?" | "#NUM!" | "#N/A";
}
type SheetVisibility = "visible" | "hidden" | "very-hidden";
interface Sheet {
name: string;
visibility: SheetVisibility;
rows: Cell[][]; // dense, null-padded to the last used column
}
interface Workbook {
sheets: Sheet[];
}- Numbers come back as
number(bothNUMBERand compactRKencodings). - Dates — a number whose cell format is a date/time (built-in or custom) is
returned as a
Date(UTC). UseexcelSerialToDateif you need the raw conversion. - Error cells come back as a
CellError(e.g.#DIV/0!,#N/A) — checkcell instanceof CellErrorand readcell.code. In JSON they serialize to{ "code": "#DIV/0!" }. - Blank cells are
null. - Visibility — each sheet reports whether it's
visible,hidden, orvery-hidden, so you can skip the hidden lookup/config tabs. Chart, macro, and VBA substreams aren't worksheets and are left out ofworkbook.sheets.
| xls-reader | SheetJS (xlsx) |
ExcelJS | |
|---|---|---|---|
Reads legacy .xls (BIFF8) |
✅ | ✅ | ❌ |
Reads .xlsx (OOXML) |
❌ | ✅ | ✅ |
| Latest fixes on the npm registry | ✅ | 0.18.5 |
✅ |
| Runtime dependencies | 0 | several | several |
| Install size | ~4 KB min+gzip | large | large |
| npm provenance attestation | ✅ | ❌ | ❌ |
| Writes files / styling / charts | ❌ | ✅ | ✅ |
If you need to write spreadsheets, style cells, or read .xlsx, reach for
ExcelJS or SheetJS. xls-reader deliberately does one thing: get the values out
of a legacy .xls.
- OLE2 / Compound File container (regular and mini streams).
- BIFF8 records:
LABELSST,LABEL,RSTRING,NUMBER,RK,MULRK,BLANK,MULBLANK,BOOLERR,FORMULA(+ itsSTRINGresult). - Shared-string table (
SST) including strings split acrossCONTINUErecords. - Date detection via
XF+FORMAT, and the 1900 / 1904 date systems. - Sheet visibility (
visible/hidden/very-hidden); chart, Excel-4 macro, and VBA substreams are recognized and skipped (they aren't worksheets).
- Read-only, and reads values — not styling, merged-cell geometry, or charts.
- BIFF8 only (Excel 97+). It does not read the much older BIFF5/BIFF2, nor the
HTML/XML files some tools mislabel as
.xls(check the magic bytes). - Encrypted workbooks are not supported.
- Dates before 1900-03-01 in the 1900 system are off by one day (Excel's historical leap-year bug); real-world data is unaffected.
Install xls-reader, read the file into bytes, and call readXls — no other
setup, no native modules:
import { readFile } from "node:fs/promises";
import { readXls } from "xls-reader";
const workbook = readXls(await readFile("report.xls"));For reading legacy .xls, yes. SheetJS no longer publishes to npm, so
xls-reader is a small, npm-published, zero-dependency option when all you need
is the cell values. It does not write files or read .xlsx — for that, use
SheetJS or ExcelJS.
No. .xlsx is the newer OOXML (zipped XML) format — a completely different
container. xls-reader handles the older binary BIFF8 .xls only. Use
ExcelJS for .xlsx.
See Convert an .xls to JSON above — call
sheetToObjects(sheet) to key each data row by the header row.
Yes. It only uses Uint8Array / DataView, so pass an ArrayBuffer (e.g. from
a file <input> or fetch) straight to readXls.
Bug reports, sample files, and PRs are very welcome — see CONTRIBUTING.md. Please also read the Code of Conduct. To report a security issue, see the Security Policy.
MIT © Thiago Zanluca