TabularJS
Alternatives

TabularJS vs ExcelJS

ExcelJS is a full-featured Excel toolkit — read, write, style, stream — with a workbook-centric object model. If all you need is the data out of a file, that model becomes boilerplate: open the workbook, pick the sheet, iterate rows, trim off the 1-indexed padding. TabularJS collapses that into one async call, with the same output shape across 16+ formats.

At a glance

FeatureTabularJSExcelJS
DependenciesZeroSeveral runtime dependencies
LicenseMITMIT
Formats read16+ (XLSX, XLS, ODS, CSV, HTML, DBF, SYLK, DIF, Lotus)XLSX, CSV (primarily)
API surfaceSingle await tabularjs(file) callWorkbook → worksheets → rows → cells
Output shapeArray-of-arrays, drop-in for JspreadsheetRow objects (1-indexed)
Write / mutate workbooksRead-onlyYes — rich styling & streaming writers
Streaming readerNoYes

Stick with ExcelJS if you are generating Excel files, styling them on the server, or streaming rows through gigabyte-scale workbooks. Switch to TabularJS when the goal is simply to read a file into JSON.

Migration: Node.js

A typical ExcelJS read pulls data out row by row, mindful of the 1-indexed row.values:

exceljs.jsjs
import ExcelJS from 'exceljs';

const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('./sales.xlsx');

const worksheet = workbook.getWorksheet(1);
const rows = [];

worksheet.eachRow({ includeEmpty: true }, (row) => {
    // row.values is 1-indexed; drop the first slot
    rows.push(row.values.slice(1));
});

console.log(rows);

The TabularJS version is a single call:

tabularjs.jsjs
import tabularjs from 'tabularjs';

const result = await tabularjs('./sales.xlsx');

console.log(result.worksheets[0].data);

Migration: every worksheet

before.jsjs
// ExcelJS — iterate every worksheet
await workbook.xlsx.readFile('./book.xlsx');

const sheets = workbook.worksheets.map((ws) => {
    const data = [];
    ws.eachRow({ includeEmpty: true }, (row) => data.push(row.values.slice(1)));
    return { name: ws.name, data };
});
after.jsjs
// TabularJS — same thing, already shaped
const { worksheets } = await tabularjs('./book.xlsx');
// worksheets: [{ name, data, mergeCells, styles }, ...]

TabularJS already returns an array of worksheets in the order they appear, each with name, data, mergeCells and styles.

Migration: browser upload

before.jsjs
// ExcelJS browser upload
input.addEventListener('change', async (e) => {
    const buffer = await e.target.files[0].arrayBuffer();
    const workbook = new ExcelJS.Workbook();
    await workbook.xlsx.load(buffer);

    const ws = workbook.worksheets[0];
    const rows = [];
    ws.eachRow({ includeEmpty: true }, (row) => rows.push(row.values.slice(1)));

    render(rows);
});
after.jsjs
// TabularJS browser upload
input.addEventListener('change', async (e) => {
    const result = await tabularjs(e.target.files[0]);
    render(result.worksheets[0].data);
});

No ArrayBuffer conversion. No row padding to trim. And the same code now handles CSV, ODS, HTML tables and older formats without any branches.

When to stay with ExcelJS

In many codebases the practical choice is to use TabularJS on the read path (uploads, imports, ETL) and keep ExcelJS for the write path — they coexist cleanly.

Start migrating

Replace your ExcelJS read calls with a single await tabularjs(file).