A few weeks ago I had a boring task: read some old .xls files (the Excel 97–2003 kind) and pull the numbers out. I figured this was a solved problem in 2026. It wasn't, quite.
ExcelJS, which most people reach for, only reads .xlsx. The old .xls binary format isn't in scope. Fair enough.
So I went for SheetJS, the library everyone points to for .xls. That's when I hit the real surprise: it's not on npm anymore. The last version on the registry is 0.18.5, it's frozen, and it has known advisories. The fixed builds live on their own CDN. Installing from a CDN is fine until you realize npm audit and Dependabot can't see it, so you quietly lose your vulnerability alerts for a library that parses untrusted binary files. That felt wrong for something going into a work project.
I looked around for a small, npm-published option that just reads the cells outof an .xls. Didn't find one I was happy with. So I wrote it.
It's called xls-reader:
npm install xls-reader
import { readFile } from "node:fs/promises";
import { readXls } from "xls-reader";
const workbook = readXls(await readFile("report.xls"));
for (const sheet of workbook.sheets) {
for (const row of sheet.rows) {
console.log(row); // ["BANCO X S/A", 1.1, 2024-04-02T00:00:00.000Z, ...]
}
}
Cells come back already typed: strings, numbers, booleans, and dates as real Date objects. Blank and error cells are null.
If you need the rows as objects, using the header row as keys:
import { readFirstSheet } from "xls-reader";
const sheet = readFirstSheet(bytes);
const [header = [], ...body] = sheet?.rows ?? [];
const json = body.map((row) =>
Object.fromEntries(header.map((key, i) => [String(key), row[i] ?? null])),
);
A few things I cared about while building it:
- No runtime dependencies. It's about 4 KB min+gzip. It only uses Uint8Array and DataView, so it also runs in the browser (handy for a file ).
- Published to npm with provenance. You can verify the build came from the repo's CI with npm audit signatures. This was the whole point for me.
- TypeScript, dual ESM/CJS.
I want to be honest about what it does not do, because it's narrow on purpose: it's read-only, it reads values (not styles, charts, or merged-cell layout), and it only handles BIFF8. If you need to write files or read .xlsx, ExcelJS or SheetJS are still the right call.
If you've got an old .xls sitting around that it can't read, I'd genuinely like to know. A small sample file on the issues page helps a lot.
Repo: https://github.com/zanlucathiago/xls-reader
npm: https://www.npmjs.com/package/xls-reader
Thanks for reading.
Top comments (0)