Skip to content

Repository files navigation

bun-excel

CI npm version GitHub stars License: MIT Bun TypeScript

English 中文

High-performance, Bun-optimized Excel and CSV library for TypeScript.

⚠️ Note: Runtime note: bun-excel uses Bun-specific APIs. It is intended for Bun and is not compatible with Node.js or Deno.

Please read our Code of Conduct before participating in the project.

Some security scanners may flag schemas.openxmlformats.org or similar URLs in this package. These are OOXML namespace and relationship identifiers required by the Excel file format, not runtime network requests.

Why This Package

  • Built for Bun, not adapted from Node-first abstractions — The core file paths use Bun.file(), Bun.write(), FileSink, and Bun-native streaming APIs directly.
  • Works naturally with Bun-native file targets, including S3 — Read from and write to local paths, Bun.file(...), and Bun S3File objects, including direct streaming exports to S3 destinations.
  • Production export helpers for Bun backends — Supports progress callbacks, AbortSignal, export diagnostics, streaming Response helpers, and S3 multipart tuning through Bun-native writer options.
  • TypeScript-first spreadsheet modelWorkbook, Worksheet, Row, Cell, and style objects are explicit and practical to work with in Bun apps.
  • Focused on real report workflows — Styles, formulas, hyperlinks, data validation, conditional formatting, auto filters, freeze/split panes, and workbook metadata are supported where they matter for business exports.
  • Multiple write strategies for different workloads — Use normal writes for simplicity, stream writes for lower memory pressure, and chunked disk-backed writes for large exports.

Install

bun add bun-excel

Requires Bun 1.4.0 or newer.

Quick Start

Write Excel

import { writeExcel, type Workbook } from "bun-excel";

const workbook: Workbook = {
  worksheets: [{
    name: "Sheet1",
    columns: [{ width: 20 }, { width: 15 }],
    rows: [
      {
        cells: [
          { value: "Name", style: { font: { bold: true } } },
          { value: "Score", style: { font: { bold: true } } },
        ],
      },
      { cells: [{ value: "Alice" }, { value: 95 }] },
      { cells: [{ value: "Bob" }, { value: 87 }] },
    ],
  }],
};

await writeExcel("report.xlsx", workbook);

Read Excel

import { readExcel } from "bun-excel";

const workbook = await readExcel("report.xlsx");
for (const sheet of workbook.worksheets) {
  console.log(`Sheet: ${sheet.name}`);
  for (const row of sheet.rows) {
    console.log(row.cells.map(c => c.value).join(" | "));
  }
}

For previews and selective imports, readExcelStream() supports row ranges, per-sheet row limits, column selection, cancellation and progress callbacks.

Use readExcelInfo() to inspect sheet names, visibility and workbook metadata before importing rows.

Use readExcelObjectsStream() to map headers to typed objects and receive cell-level validation errors while streaming.

Use readExcelValuesStream() for bounded batches of values when importing data without cell metadata.

readExcel() can skip images, comments and tables with includeImages: false, includeComments: false and includeTables: false.

Password-to-open encryption is available through writeExcel(target, workbook, { password }) and buildExcelBuffer(workbook, { password }). See encrypted exports for supported APIs and memory requirements.

CSV

import { readCSV, writeCSV } from "bun-excel";

// Write
await writeCSV("data.csv", [
  [{ value: "Name" }, { value: "Age" }],
  [{ value: "Alice" }, { value: 28 }],
]);

// Read
const csv = await readCSV("data.csv");

Documentation

See DOCUMENT.md for the complete API reference, including:

  • All functions (writeExcel, readExcel, writeCSV, readCSV, streaming APIs)
  • Type definitions (Workbook, Worksheet, Cell, Row, etc.)
  • Styles guide (font, fill, border, alignment, number formats)
  • Features (formulas, hyperlinks, merge cells, freeze panes, data validation)
  • Writing modes comparison (normal vs streaming vs chunked)

Benchmarks

Measured on Bun 1.4.0 / macOS ARM64 on 2026-09-04. Values are medians of 3 runs, with each mode in a fresh process. The write workloads export a single compressed .xlsx worksheet.

1,000,000 rows × 10 columns

Mode Total time Finalize time Rows/sec Peak RSS File size
createExcelStream() 7.43s 4.01s 134,505 88.2 MiB 54.31 MiB
createChunkedExcelStream() 6.80s 3.28s 147,004 94.4 MiB 54.31 MiB
bun run benchmark:1m

Large report: 30,000 data rows × 30 columns, including styles, merged cells and footer formulas from examples/large-report.ts.

Method Total time Peak RSS Sampled peak heapUsed File size
writeExcel() 0.86s 337.0 MiB 121.8 MiB 5.90 MiB
createExcelStream() 0.94s 81.7 MiB 6.3 MiB 6.03 MiB
createChunkedExcelStream() 0.96s 82.5 MiB 8.5 MiB 6.03 MiB
bun run benchmark

Streaming reads: readExcelStream()

Previous reader (custom XML parser) versus the current Bun XML native reader, on the same Bun 1.4.0 runtime. Each fixture contains 30,009 rows, including report headers and footers. Results are medians of 3 alternating runs per implementation in fresh processes; time includes reading all rows and hashing their JSON output. Output checksums matched.

XLSX fixture Previous time Native time Previous peak RSS Native peak RSS
Shared strings (bench-normal.xlsx) 2.107s 1.709s 184.7 MiB 174.6 MiB
Inline strings (bench-stream.xlsx) 2.214s 1.709s 153.5 MiB 149.4 MiB

This compares the complete readers, including XML batching and ZIP decompression, rather than XML parsing alone.

Values-only batch reads

Measured on Bun 1.4.0 / macOS ARM64 on 2026-09-05: medians of three alternating runs per reader in fresh processes. Both paths extract and hash the same values, including original sheet/row indices; SHA-256 checksums matched. The baseline is the current readExcelStream() plus value extraction. This workload differs from the full-row hashing comparison above.

Fixture Row reader + values Values batches Row reader peak RSS Values batches peak RSS
Report, shared strings · 30,009 rows 1.135s 1.034s 166.6 MiB 154.9 MiB
Report, inline strings · 30,009 rows 1.133s 1.017s 155.6 MiB 133.5 MiB
Inline strings · 1,000,001 rows 12.382s 11.207s 176.7 MiB 161.3 MiB

readExcelValuesStream() uses default options. RSS varies between runs; these are measured medians, not memory ceilings. Reproduce with bun run examples/benchmark-read-values.ts output/bench-normal.xlsx, substituting bench-stream.xlsx or benchmark-stream-1m.xlsx for the other fixtures. Generate fixtures with bun run benchmark and bun run benchmark:1m.

Peak RSS is the OS-recorded process maximum, including runtime memory; heapUsed is sampled and may miss brief peaks. Memory and file sizes use MiB. Results vary by machine and system load; RSS values are not directly comparable to the previous shared-process memory deltas.

Examples

# Benchmarks (normal vs stream vs chunked)
bun run benchmark

# 1M-row Excel benchmark (stream vs chunked)
bun run benchmark:1m

Security

This library is security-hardened:

  • XML bomb prevention — Depth limits, node count caps, input size validation
  • Path traversal protectionpath.resolve() + null byte checks on all file paths
  • Zip slip prevention — Validates all paths within ZIP archives
  • Input validation — Max rows (1M), max columns (16K), max file size (200MB)
  • XML injection prevention — All user values properly escaped
  • Prototype pollution preventionObject.create(null) for dynamic maps

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT

About

High-performance, Bun-optimized Excel and CSV library for TypeScript. Work with Bun S3

Topics

Resources

Code of conduct

Contributing

Stars

16 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages