Blog

Import XLSX, CSV, and ODS files into a JavaScript spreadsheet in your browser

Allow users to drag any spreadsheet file into your webpage and see an editable table directly. No backend required, no npm audits, and no need to read the commercial license terms of Excel parsers.

Published at 20/04/2026

"Isn't it just parsing XLSX?"

The product manager tells you: "Users want to upload Excel files and edit them directly in the browser." You say, "Okay, sounds like it can be done by the weekend." Then you open npm.

The XLSX parsing library was loaded with a bunch of dependencies. The CSV parser only handled CSV. ODS was another library. Each output format produced data in a different shape, so you had to write a lot of glue code to unify them into a format usable by the front-end spreadsheet. Three days' work turned into two weeks' worth.

XLSX, CSV, and ODS files streaming into a Jspreadsheet grid via TabularJS

TabularJS: one function, 16 formats, unified JSON output

TabularJS is a small MIT-licensed library that reads spreadsheet files and returns a uniformly formatted JSON object. It can handle over a dozen formats, including XLSX, XLS, ODS, CSV, TSV, SYLK, DIF, dBase, Lotus 1-2-3, XML 2003, and HTML tables, all of which produce the same JSON structure.

With the help of Jspreadsheet CE, which uses JSON as a data source, a file importer can be created with just fifteen lines of code.

This is what TabularJS output looks like

{
  worksheets: [
    {
      name: "Sheet1",
      data: [
        ["Name",  "Department",  "Salary"],
        ["Alice", "Engineering", 95000],
        ["Bob",   "Marketing",   72000]
      ],
      styles: { "A1": { "bold": true } },
      mergeCells: { "A5:C5": 1 },
      comments: { "B2": "Approved" }
    }
  ]
}

The data field is a two-dimensional array that Jspreadsheet can directly use. The key naming rules for styles and mergeCells are also consistent with those in the Jspreadsheet configuration, and can be passed through as is.

A complete, running example

Save the following as an HTML file, open it in a browser, and upload any Excel file from your hard drive. It works offline as well.

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="https://jsuites.net/v5/jsuites.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce/dist/jspreadsheet.css">
</head>
<body>
    <input type="file" id="file" accept=".xlsx,.xls,.ods,.csv,.tsv">
    <div id="spreadsheet"></div>

    <script src="https://jsuites.net/v5/jsuites.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce/dist/index.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/tabularjs/dist/index.js"></script>

    <script>
    const container = document.getElementById('spreadsheet');
    let grid;

    document.getElementById('file').addEventListener('change', async (e) => {
        const file = e.target.files[0];
        if (!file) return;

        const { worksheets } = await tabularjs(file);
        const [headers, ...rows] = worksheets[0].data;

        if (grid) grid.destroy();

        grid = jspreadsheet(container, {
            worksheets: [{
                data: rows,
                columns: headers.map(title => ({ title: String(title), width: 140 }))
            }]
        });
    });
    </script>
</body>
</html>

That's all the code. The file input box accepts the file extensions you list, TabularJS automatically recognizes the format, and Jspreadsheet is responsible for rendering the first worksheet.

How to handle multiple sheet files

A real XLSX file typically contains more than one sheet. TabularJS will place all the sheets into a worksheets array in the order they appear in the document. Passing this entire array to Jspreadsheet results in a table with tabbed navigation.

const { worksheets } = await tabularjs(file);

grid = jspreadsheet(container, {
    tabs: true,
    worksheets: worksheets.map(ws => {
        const [headers, ...rows] = ws.data;
        return {
            worksheetName: ws.name,
            data: rows,
            columns: headers.map(title => ({ title: String(title), width: 140 }))
        };
    })
});

When a user uploads a sales report with three sheets, three tabs appear on the interface. Users can click around and edit any cell; the data resides in memory, and you can push it to the backend whenever you want.

Change to drag and drop upload

Want to create an experience where you can drag and drop files anywhere on the page? TabularJS requires the same thing: the file object and the files[0] in the drop event. Just change the listener.

const dropZone = document.getElementById('drop');

dropZone.addEventListener('dragover', (e) => e.preventDefault());

dropZone.addEventListener('drop', async (e) => {
    e.preventDefault();
    const file = e.dataTransfer.files[0];
    if (!file) return;

    const { worksheets } = await tabularjs(file);
    const [headers, ...rows] = worksheets[0].data;

    grid = jspreadsheet(container, {
        worksheets: [{
            data: rows,
            columns: headers.map(title => ({ title: String(title), width: 140 }))
        }]
    });
});

TabularJS is called in the same way. The drag-and-drop area and the file input box can be attached simultaneously. The user clicks to select a file, then drags it into the drop zone.

Pulling files from a remote address

The browser's file input field is not the only source. If your application downloads a spreadsheet from a URL, pass the response blob directly to TabularJS:

const response = await fetch('/reports/q1-sales.xlsx');
const blob = await response.blob();
const { worksheets } = await tabularjs(blob);

Any API that returns a binary spreadsheet will work, including S3 pre-signed links and authenticated API routes. The same code will also run in Node.js, and you can use the same approach if you're doing server-side imports.

What will happen with the formulas?

Formulas in XLSX, XLS, ODS, SYLK, XML 2003, and HTML files will appear as strings in the data array, with the same format as formulas that Jspreadsheet can directly recognize. If a cell in your uploaded file contains =SUM(A1:A10), the rendered table will take over this formula and continue the calculation.

The CE version can directly reference formulas like =B2*C2 and =A1+B1. For named functions like SUM, VLOOKUP, and INDEX/MATCH, you need to use the full formula engine in Jspreadsheet Pro. If CE encounters a formula it can't calculate, the cell will directly display the formula string; it won't crash or display an error, but it simply won't calculate it.

Format support overview

FormatExtensionDataFormulaStyle
Excel 2007+.xlsxYesYesYes
Excel 97-2003.xlsYesYesPartial
OpenDocument.odsYesYesYes
CSV.csvYesNoNo
TSV.tsvYesNoNo
SYLK.slkYesYesNo
DIF.difYesNoNo
dBase.dbfYesNoNo
Lotus 1-2-3.wk1, .wksYesPartialNo
XML 2003.xmlYesYesPartial
HTML Table.htmlYesYesPartial

All formats output the same structure: { worksheets: [{ data, styles, mergeCells }] }. The rendering code is written only once, without needing to branch according to format.

Why this combination is suitable for open source projects

For those of you who came from xlsx or SheetJS, please take a look and see where this combination makes sense.

  • Two MIT licenses. TabularJS and Jspreadsheet CE are both MIT-licensed and can be used directly in commercial projects without legal procedures.
  • Clean transitive dependencies. TabularJS's node_modules is empty, CE only has a sibling dependency, jSuites, and npm audit doesn't cause any noise.
  • Browsers and Node use the same API. The browser importer I wrote today can be moved into the Node pipeline next quarter without any code changes.
  • The files don't leave the browser. Unless you actively send the data to the backend, user-uploaded files remain locally. For compliance-sensitive data, this is the whole point.

A few details to mention

Headers that aren't on row 1. If the first row of the workbook is not a header, for example, if the first few rows are titles or blank lines, TabularJS returns it as is, and you will need to cut it out first manually.

const rawData = worksheets[0].data;
const headerRowIndex = rawData.findIndex(row => row.some(cell => cell));
const headers = rawData[headerRowIndex];
const rows = rawData.slice(headerRowIndex + 1);

Date cells. The dates in XLSX are stored as serial numbers. After TabularJS parses them, it converts them to ISO strings, which can then be directly added to a calendar type column.

columns: [
    { title: 'Date', type: 'calendar', width: 140 },
    { title: 'Amount', type: 'numeric', width: 120 }
]

Merged cells. The mergeCells object for merging cells uses the A1 key, and Jspreadsheet expects the same format, allowing for pass-through.

grid = jspreadsheet(container, {
    worksheets: [{
        data: rows,
        mergeCells: worksheets[0].mergeCells,
        columns: headers.map(title => ({ title: String(title), width: 140 }))
    }]
});

Very large files. When dealing with very large files, be aware that TabularJS parses synchronously, and a 50 MB XLSX file can cause the browser to freeze for a second or two. For files larger than 10 MB, it's recommended to use a Web Worker for parsing or to display a loading animation in the UI first.

Reverse operation: export back to XLSX

Importing is only half the story. After modifying the data in CE, the user will definitely want to export it. The array shape returned by CE's getData() is the same as that produced by TabularJS, so closing the loop is easy:

const data = grid.getData();
// hand `data` off to any exporter you use

CE's built-in download method can directly save it as a CSV file, which is sufficient for simple needs:

grid.download('my-file.csv');

For XLSX writing, we suggest using the Jspreadsheet Pro Render extension or running Python/Node on the server side. Browser-side XLSX writers are somewhat heavyweight.

When not to use TabularJS

If you only need to receive CSV files, FileReader with split is sufficient; no additional libraries are required. TabularJS is read-only; if you need to generate an XLSX file, you'll need to use a different tool or the Jspreadsheet Pro Render extension. Advanced features like pivot tables, charts, and conditional formatting are beyond the capabilities of browser-side parsers and require a server-side approach.

The most common scenario is that a user uploads a spreadsheet and sees an editable grid on the page. In this case, TabularJS + CE is probably the lightest setup you can build.

Try it

npm install tabularjs

Alternatively, you can use the CDN scripts above directly. This entire example requires no packaging tools, no framework, and no package.json file.

The format support table is the current state of TabularJS 1.x. Newer formats will be added to the same function signature, so your code won't need to be modified.

Related resources