HomeFeaturesDrawDemoDocumentationPricingContact

Import & Export Excel (XLSX) Files in JavaScript

An XLSX file flowing through an import and export pipeline into a data grid

Reading and writing Excel files in JavaScript is easy for raw values and surprisingly hard for everything else — styles, formulas, merged cells, and images. This guide covers importing and exporting XLSX, CSV, and JSON in the browser and on the server, the round-trip fidelity problem that bites most teams, and how to decide between a file parser and a full spreadsheet engine.

Almost every data-heavy app eventually needs to read an uploaded spreadsheet or hand the user a downloadable .xlsx. JavaScript can do both — in the browser and on Node — but the gap between 'parse the numbers' and 'preserve the workbook' is where most projects underestimate the work.

This guide walks through importing and exporting XLSX, CSV, and JSON, the round-trip fidelity problem, the browser-versus-server tradeoff, and when a file parser is enough versus when you actually need a spreadsheet engine.

How do you read an XLSX file in JavaScript?

An .xlsx file is a zip of XML parts, so you never want to parse it by hand. In the browser you read the uploaded file as an ArrayBuffer; on Node you read it from disk as a Buffer. The parsing call is the same either way:

// Browser: from a file <input>
async function readWorkbook(file: File) {
  const buffer = await file.arrayBuffer();
  const workbook = parseXlsx(buffer); // your parser of choice
  const sheet = workbook.sheets[0];

  // Iterate rows -> array of objects keyed by header
  const [headers, ...rows] = sheet.toArray();
  return rows.map((row) =>
    Object.fromEntries(row.map((cell, i) => [headers[i], cell]))
  );
}

// Node: from disk
import { readFile } from "node:fs/promises";
const buffer = await readFile("./report.xlsx");
const workbook = parseXlsx(buffer);

That handles values. What it quietly drops is everything that makes the file a spreadsheet: number formats, cell styles, formulas (you usually get the cached result, not the formula), merged ranges, frozen panes, and embedded images.

How do you export an XLSX file for download?

Exporting is the reverse: build a workbook structure in memory, serialize it to a binary blob, and trigger a download in the browser (or write it to disk on the server).

function exportToXlsx(rows: Record<string, unknown>[]) {
  const headers = Object.keys(rows[0]);
  const matrix = [headers, ...rows.map((r) => headers.map((h) => r[h]))];

  const blob = buildXlsx(matrix); // -> Blob (application/vnd...sheet)

  // Trigger a browser download
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "export.xlsx";
  a.click();
  URL.revokeObjectURL(url);
}

This produces a valid file Excel will open. But it is a flat dump of values — no column widths, no currency or date formatting, no styling. If your users expect the export to look like what they saw on screen, a plain value dump will disappoint them.

CSV and JSON

  • CSV is text, so it carries zero formatting, formulas, or types — every value is a string and you parse types yourself. Watch for quoting, embedded commas, newlines in cells, and the BOM Excel expects for UTF-8.
  • JSON is the natural shape for app state and APIs, but it has no native concept of a sheet, a formula, or a style — you define your own schema and lose anything you don't model.
  • Both are great for data interchange and terrible for fidelity. Reach for them when you only care about values, not appearance.

What is round-trip fidelity, and why does it break?

The real difficulty is not reading or writing — it is preserving the workbook when a file goes in and comes back out. A user uploads a styled, formula-driven .xlsx, edits a few cells, and downloads it again. With a naive read-then-write pipeline, the file that comes back has lost its formulas (replaced by stale values), its number formats, its merged cells, and its images.

Fidelity loss is silentMost parsers happily read a value cell and discard the formula, format, and style that produced it. Nothing errors — the data looks correct — but the re-exported file is degraded. If round-trip integrity matters, verify it explicitly with a real, richly formatted workbook, not a toy CSV.

Preserving fidelity means modeling the whole workbook — styles, formats, formulas, defined names, merges, images — not just a 2D array of values. That is a materially bigger job than a CSV parser, and it is the line that separates a file toolkit from a spreadsheet engine.

Should you parse Excel in the browser or on the server?

BrowserServer (Node)
File never leaves deviceYes — good for privacyNo — upload required
Large filesLimited by tab memoryScales with the box
Generated downloadsInstant, no round-tripStream the response
CPU-heavy parsingCan freeze the UIOffload to a worker/queue
Best forEditing and instant exportBatch jobs, big data, automation

In the browser, keep heavy parsing off the main thread with a Web Worker so the UI stays responsive. On the server, treat very large imports as background jobs rather than blocking a request. Many apps do both: parse small files client-side for instant feedback, and route large or trusted batch imports through the server.

Parser vs. engine: which do you need?

There are two distinct tools here, and picking the wrong one wastes weeks.

A file toolkit like SheetJS reads and writes a huge range of formats — 20+ including XLSX, CSV, ODS, and more. The Community build is free under Apache-2.0, with a paid Pro tier adding extras; it reads and writes formula text but does not recalculate formulas for you. Crucially, it has no UI: it converts files to and from data, and that is the whole job. If all you need is to ingest an upload or generate a download, that is exactly the right scope.

A spreadsheet engine does that and adds an interactive grid: the user sees the sheet, edits cells, formulas recalculate, and the export reflects what they edited. WorksheetJS does full-fidelity XLSX, CSV, and JSON import-export entirely in the browser — styles, formulas, and images preserved on round-trip — and renders an editable spreadsheet on top. Install it from npm as @worksheet-js/core, with framework bindings like @worksheet-js/react.

import { Worksheet } from "@worksheet-js/react";

// `ws` is the mounted <Worksheet /> instance (accessed via ref)

// Import: parse an uploaded file (.xlsx, .csv, .html) — styles + formulas intact
await ws.importFromFile(file);

// ...user views and edits the live spreadsheet...

// Export: round-trip back to .xlsx with fidelity preserved (triggers a download)
await ws.exportXlsx("edited.xlsx");

// Or get a buffer to send to your server
const buffer = await ws.exportToBuffer();
await fetch("/api/save", { method: "POST", body: buffer });
ApproachGood for
CSV/JSON parsePure data interchange where appearance does not matter
XLSX parser (e.g. SheetJS)Importing or generating files server-side or client-side, no UI
Spreadsheet engine (WorksheetJS)Displaying and editing the data, with full-fidelity round-trip
Rule of thumbIf the file is just passing through your app, use a parser. If the user needs to look at it, change it, and get it back unchanged-but-edited, use an engine.

What are the common Excel import/export pitfalls?

  1. Reading the formula's cached value and assuming it will recalculate later — it will not unless something evaluates it.
  2. Forgetting the UTF-8 BOM on CSV, so Excel mangles accented characters.
  3. Treating dates as numbers (Excel stores them as serial numbers) and exporting raw serials.
  4. Parsing large files on the main thread and freezing the tab.
  5. Round-tripping through a value-only model and silently stripping styles, merges, and images.

Conclusion

Importing and exporting Excel in JavaScript is straightforward for raw values and genuinely hard for fidelity. Scope the job honestly: if you only move data, a parser is enough; if users will see and edit the sheet and expect it back intact, reach for a spreadsheet engine that handles the full-fidelity round-trip for you.

Import, edit, and export XLSX with full fidelity in the browser — free developer tier, no credit card.Get Started Free

Read More

Frequently Asked Questions

Have questions about WorksheetJs? Find answers to the most common questions about licensing, integration, and features.

Read the file as an ArrayBuffer in the browser (or a Buffer on Node), then pass it to an XLSX parser — an .xlsx file is a zip of XML parts, so never parse it by hand. The parser gives you cell values; note that styles, number formats, and live formulas are usually dropped unless you use a full spreadsheet engine.

Build a workbook in memory, serialize it to a Blob with the spreadsheet MIME type, create an object URL, and trigger a download with a temporary anchor element. No server round-trip is needed. For an export that preserves formatting and formulas, use a spreadsheet engine rather than a plain value dump.

SheetJS is a file toolkit: it converts files to and from data across 20+ formats but has no UI and doesn't recalculate formulas. WorksheetJS is a spreadsheet engine: it does full-fidelity XLSX/CSV/JSON import-export and also renders an editable grid where formulas recalculate. Use SheetJS when files just pass through; use WorksheetJS when users need to see and edit the sheet.

Most parsers read a cell's cached value and silently discard the formula, number format, and style that produced it — nothing errors, but the re-exported file is degraded. Preserving them requires modeling the whole workbook (styles, formats, formulas, merges, images), which is what a full-fidelity spreadsheet engine does.

Build your spreadsheet with WorksheetJS

550+ formulas, an AI copilot, charts and pivots — drop a full spreadsheet into your app. Free dev tier, no credit card.

Get Started Free
Illustration of a spreadsheet panel with a donut chart and AI copilot bubble