v0 · MIT · stable / Node 22+ · browsers

Read & write Excel .xlsx from Node and the browser.

Full workbook model — values, formulas, styles, charts, drawings, pivots, VBA — plus a streaming writer that pushes 10M rows under a 100 MB heap. No Python. No Excel. No runtime native modules.

$ pnpm add xlsx-kit / npm i xlsx-kit
rows 10,000,000 streamed under 100 MB heap
bundle ~78 KB brotli, tree-shakeable
targets Node + browser no Python, no Excel

What it does well

04 / 04
01

Round-trips real workbooks

Pivot tables, macro-enabled .xlsm, threaded comments, Power Query metadata, custom XML — anything we don't model is preserved byte-identical so Excel 365 still renders it.

02

Streaming, both directions

createWriteOnlyWorkbook deflates rows as they arrive. loadWorkbookStream walks a file once and yields each row. Browser-safe via xlsx-kit/streaming.

03

Charts & drawings, modeled

16 legacy c: chart kinds plus 8 cx: chartex kinds (Sunburst, Treemap, Waterfall, Histogram, Pareto, Funnel, BoxWhisker, RegionMap). Images auto-detect format and dimensions.

04

Tiny & tree-shakeable

xlsx-kit ≤ 120 KB brotli (currently ~78 KB). xlsx-kit/streaming ≤ 80 KB brotli (~47 KB). Every export is side-effect-free.

Two snippets to get the shape

live · type-checked

Both files below live under site/src/lib/examples/ and are type-checked by svelte-check against the real library on every build — if an API renames, the docs build fails.

01

Read + edit + write

Open an xlsx, mutate one cell, write it back — the canonical full-library round-trip.

site/src/lib/examples/basic-read-write.ts .ts
// Read an xlsx, mutate one cell, write it back.
//
// This file is imported as ?raw into the docs site so the snippet shown to
// readers is exactly what svelte-check / tsc compiled — if an API rename
// breaks this import, the docs build fails before deploy.

import { loadWorkbook, workbookToBytes } from 'xlsx-kit/io';
import { fromBuffer } from 'xlsx-kit/node';
import { setCell } from 'xlsx-kit/worksheet';
import { readFile, writeFile } from 'node:fs/promises';

const wb = await loadWorkbook(fromBuffer(await readFile('input.xlsx')));
const ref = wb.sheets[0];
if (ref?.kind === 'worksheet') {
  setCell(ref.sheet, 1, 1, 'Hello from xlsx-kit');
}
await writeFile('output.xlsx', await workbookToBytes(wb));
02

Streaming write — 10M rows

createWriteOnlyWorkbook deflates rows as they arrive. Heap stays under 100 MB.

site/src/lib/examples/streaming-write.ts .ts
// Stream millions of rows to disk in a fixed memory budget. Each row is
// deflated as it arrives — no intermediate workbook in memory.

import { toFile } from 'xlsx-kit/node';
import { createWriteOnlyWorkbook } from 'xlsx-kit/streaming';

const sink = toFile('big.xlsx');
const wb = await createWriteOnlyWorkbook(sink);
const ws = await wb.addWorksheet('Data');
ws.setColumnWidth(1, 24); // must precede the first appendRow

for (let r = 0; r < 10_000_000; r++) {
  await ws.appendRow([r, `row-${r}`, r * Math.PI]);
}
await ws.close();
await wb.finalize();

More in Getting started & Streaming.