High-performance, Bun-optimized Excel and CSV library for TypeScript.
⚠️ Note: Runtime note:bun-exceluses 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.orgor similar URLs in this package. These are OOXML namespace and relationship identifiers required by the Excel file format, not runtime network requests.
- 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 BunS3Fileobjects, including direct streaming exports to S3 destinations. - Production export helpers for Bun backends — Supports progress callbacks,
AbortSignal, export diagnostics, streamingResponsehelpers, and S3 multipart tuning through Bun-native writer options. - TypeScript-first spreadsheet model —
Workbook,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.
bun add bun-excelimport { 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);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(" | "));
}
}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");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)
Measured on Bun 1.3.10 / MacOS ARM with a single worksheet, compressed .xlsx, and 1,000,000 rows x 10 columns:
| Mode | Total time | Finalize time | Rows/sec | Peak RSS | Peak heapUsed | File size |
|---|---|---|---|---|---|---|
createExcelStream() |
13.1s |
8.9s |
76,363 |
110.6MB |
5.1MB |
54.33MB |
createChunkedExcelStream() |
11.9s |
8.5s |
84,029 |
120.9MB |
5.1MB |
54.33MB |
createExcelStream() now uses the same disk-backed low-memory path as the chunked writer for single-sheet exports, so the numbers are expected to be close. Re-run the large benchmark on your machine with:
bun run benchmark:1mFor the realistic large-report workload benchmark across normal, streaming, and chunked modes, measured on Bun 1.3.10 / MacOS ARM with a single worksheet, compressed .xlsx, and 30 columns x 30,000 rows using the same styles, merge cells, and footer formulas as examples/large-report.ts:
| Method | Total time | Peak RSS delta | Peak heapUsed delta | File size |
|---|---|---|---|---|
writeExcel() |
1.92s |
518.6MB |
154.0MB |
6.20MB |
createExcelStream() |
1.98s |
48.0MB |
39.2MB |
6.35MB |
createChunkedExcelStream() |
2.01s |
2.5MB |
3.1MB |
6.35MB |
These memory columns are peak deltas over the baseline process memory during the benchmark, not just the memory difference before and after the write.
Re-run this benchmark on your machine with:
bun run benchmark# Benchmarks (normal vs stream vs chunked)
bun run benchmark
# 1M-row Excel benchmark (stream vs chunked)
bun run benchmark:1mThis library is security-hardened:
- XML bomb prevention — Depth limits, node count caps, input size validation
- Path traversal protection —
path.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 prevention —
Object.create(null)for dynamic maps
See CONTRIBUTING.md for development setup and guidelines.