npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xlsx-stream-writer

v1.3.2

Published

Create xlsx in streaming mode in browser and nodejs, with no dependencies

Readme

xlsx-stream-writer

Create .xlsx files in streaming mode, in the browser and in Node.js.

Built for one job: writing very large spreadsheets with simple formatting, without holding all the rows in memory.

No dependencies. The ZIP container is written by this package, compressing through node:zlib on the server and CompressionStream in the browser.

Rewritten from the CoffeeScript node-xlsx-writer and changed to run in both environments. The API is completely different from that implementation.

Install

npm install xlsx-stream-writer

Requires Node.js 20.19 or newer. In the browser it needs CompressionStream (Chrome 80+, Firefox 113+, Safari 16.4+) and a bundler that honours the browser field, which is all of them.

Projects that cannot take those requirements can stay on the 0.2 line, which carries the same corruption fixes and keeps the old behaviour:

npm install xlsx-stream-writer@legacy

Coming from 0.2.x? See docs/migrating-to-1.x.md — written to be followed step by step.

Rows from an array

const XlsxStreamWriter = require("xlsx-stream-writer");
const fs = require("fs");

const rows = [
  ["Name", "Location"],
  ["Alpha", "Adams"],
  ["Bravo", "Boston"],
  ["Charlie", "Chicago"],
];

const xlsx = new XlsxStreamWriter();
xlsx.addRows(rows);

xlsx.getFile().then(buffer => {
  fs.writeFileSync("result.xlsx", buffer);
});

getFile() resolves to a Buffer in Node.js and a Blob in the browser.

Rows from a stream

addRows accepts an array, a Node.js readable stream, a web ReadableStream, or any iterable or async iterable of rows — so you can feed it a database cursor or a generator directly:

const XlsxStreamWriter = require("xlsx-stream-writer");
const fs = require("fs");

async function* fetchRows() {
  yield ["Name", "Location"];
  for await (const record of database.stream("SELECT name, location FROM city")) {
    yield [record.name, record.location];
  }
}

const xlsx = new XlsxStreamWriter();
xlsx.addRows(fetchRows());

xlsx.getFile().then(buffer => {
  fs.writeFileSync("result.xlsx", buffer);
});

If the source fails part-way through, getFile() rejects with that error.

Streaming the workbook out

getFile() builds the whole archive in memory. For workbooks too large for that, getStream() returns a ReadableStream of the archive bytes, so rows go in while bytes come out:

const { Readable } = require("node:stream");
const { pipeline } = require("node:stream/promises");
const fs = require("node:fs");

const xlsx = new XlsxStreamWriter();
xlsx.addRows(generateRows());

await pipeline(
  Readable.fromWeb(xlsx.getStream()),
  fs.createWriteStream("large.xlsx"),
);

Writing 500 000 rows × 4 columns this way produces a 12 MB file — 80 MB of worksheet XML — in about 30 seconds, with peak memory around 240 MB. Most of that is the shared-string table; with inlineStrings: true the same export peaks near 120 MB and takes half the time, at the cost of a slightly larger file. If your strings are mostly distinct, prefer inline strings.

Cell values

| Value | Written as | | ---------------------------- | ------------------------------------------------- | | string | shared string, or an inline string | | number (finite), bigint | number | | boolean | boolean — Excel shows TRUE / FALSE | | Date | Excel date serial; apply a date format style to display it as a date | | null, undefined, NaN | blank cell | | Infinity, -Infinity | blank cell — Excel has no representation for them | | anything else | its toString(); objects that have no meaningful one raise an error |

Characters that XML 1.0 cannot represent — most control characters, unpaired surrogates — are removed, since leaving them in produces a file Excel refuses to open.

A worksheet is limited to 1 048 576 rows and 16 384 columns. Exceeding either raises rather than producing a file that will not open.

Options

const xlsx = new XlsxStreamWriter({
  // Write strings into the sheet directly instead of into a shared-string
  // table. Larger output, but no string table to hold in memory.
  inlineStrings: false,

  // Cell formats, referenced by index from styleIdFunc. Index 0 is the
  // implicit default, so the first entry here is style 1.
  styles: [{ fill: "FFFF0000" }, { format: "dd.mm.yyyy" }],

  // Choose a style per cell.
  styleIdFunc: (value, columnIndex, rowIndex) => (rowIndex === 0 ? 1 : 0),

  // Deflate level, 0-9. Node only; browsers expose no level control.
  compressionLevel: 4,
});

fill is an ARGB colour like FFFF0000. format is an Excel number format string like 0.00 or dd.mm.yyyy.

Lifecycle

A writer builds one workbook: call addRows once, then getFile or getStream once. Calling either again raises, because the row stream has been consumed. Create a new XlsxStreamWriter for the next workbook.

Archives are reproducible — entry timestamps are fixed rather than "now", so the same rows always produce the same bytes.

Plans

Shipped items live in the changelog; what is left:

  • A prebuilt browser bundle on a CDN. The package works through any bundler today, but there is nothing to drop into a <script> tag.
  • A smaller shared-string table. It is the memory ceiling on large exports — 500k rows peak around 240 MB with it and 120 MB with inlineStrings. Worth either a cheaper structure or spilling it once it outgrows a threshold.
  • Multiple sheets. The most common request the current one-sheet shape cannot answer.
  • Column widths and a frozen header row. Small, and the two bits of formatting people ask for immediately after the data lands.

TypeScript

The package is written in TypeScript and ships its own declarations — no @types package needed:

import XlsxStreamWriter = require("xlsx-stream-writer");

const styles: XlsxStreamWriter.CellStyle[] = [{ format: "dd.mm.yyyy" }];
const rows: XlsxStreamWriter.Row[] = [["When"], [new Date()]];

const xlsx = new XlsxStreamWriter({ styles });
xlsx.addRows(rows);
const buffer = await xlsx.getFile();

Contributing

npm ci
npm test        # builds, then runs the suite and the type checks
npm run build   # tsc only

Tests run against the compiled dist/, so they exercise the artifact that actually ships — go through npm test rather than the test runner directly, or you will be reading whatever was built last.

Security

Reporting and threat model: SECURITY.md.

License

MIT