typed-xlsx

Excel reports,built to spec.

1 row to 1,048,576 rows

Type-safe schemas that render polished XLSX reports for TypeScript apps. Buffered or streamed, with zero runtime dependencies.

Get started
A
B
C
D
E
F
1
2
3
4
5
6
7
8
9
10
Halvorsen Freight
12
480.00
Okapi Health
4
1,250.00
Brightwater Labs
30
96.50
Tanaka Robotics
8
720.00
Quarry Lane Studio
2
3,400.00
Nordvik Energy
16
310.00
Pell Street Bank
6
1,875.00
Due
Overdue
Due
=ROUND(C3*D3,2)5,760.00
=ROUND(C4*D4,2)5,000.00
=ROUND(C5*D5,2)2,895.00
=ROUND(C6*D6,2)5,760.00
=ROUND(C7*D7,2)6,800.00
=ROUND(C8*D8,2)4,960.00
=ROUND(C9*D9,2)11,250.00
Customer
Billing
Signal
Customer
Status
Qty
Unit price
Total
Trend
Total
78
=SUM(E3:E9)42,425.00
NO.PART
INVOICE REPORT
schema
createExcelSchema<Invoice>()
parts
6 layers, 1 schema
scale
1 : 1,048,576 rows
rendered sheet

Same schema. Any size.

1
rows in one sheetone in-memory pass
createWorkbook()buffered
createWorkbookStream()streaming
schema: invoiceSchemaunchanged
MEASURED ON THE STREAMING ENGINE
200,000 rows3.72s274MiB
500,000 rows9.30s279MiB
vs 4.2.0 on 500,000 rows3.3× faster

11 columns with styles, dates, formats and summaries, inline strings, 5,000-row batches. Node 24, Apple M5. Time and peak memory; 200k is the median of three runs.

Effortlessly beautiful.
Down to the cell.

Badges, sparklines, images, validation, grouped headers, totals. Every one is a schema field, so every report your app ships looks the same way.

STRUCTURE

DATA

VISUALS

E4fxAt risk
A
B
C
D
E
F
G
H
I
J
K
L
M
1
2
3
4
5
6
7
8
9
10
Account
Contract
Revenue
Usage %
Health
Account
Owner
Plan
Status
Prior ARR
ARR
Δ ARR
Jul
Aug
Sep
Trend
Signed
Ama Okafor
Enterprise
Renewed
$162,500
$184,000
+$21,500
71
78
84
Léa Moreau
Growth
At risk
$70,800
$62,400
-$8,400
64
58
51
Jonas Lindqvist
Enterprise
Analytics add-on
Renewed
$214,000
$241,900
+$27,900
88
91
95
Sami Haddad
Starter
Pending
$18,600
$18,600
$0
40
44
47
Ama Okafor
Growth
Renewed
$88,000
$96,250
+$8,250
66
70
69
Léa Moreau
Enterprise
Churned
$132,000
$0
-$132,000
52
31
12
Total
$685,900
$603,150
-$82,750
3 of 6
Starter
Growth
Enterprise
10
RenewalsPipelineBoard+

Wrong column?
It won't compile.

Accessors, formula references and summaries are checked against your row type. A typo fails in your editor, not in someone's spreadsheet.

invoice.schema.tsTypeScript 6.0
type Invoice = { customer: { name: string } qty: number unitPrice: number} const invoices = createExcelSchema<Invoice>() .column("customer", { accessor: "customer.name" }) .column("qty", { accessor: "qty" }) .column("unitPrice", { accessor: "unitPrice" }) .column("total", { formula: ({ refs, fx }) => fx.round(refs.column("qty").mul(refs.column("unitPrice")), 2), summary: (s) => [s.formula("sum")], }) .build()
TS2345 Argument of type '"untiPrice"' is not assignable to parameter of type '"customer" | "qty" | "unitPrice"'.
customerstring
qtynumber
unitPricenumber
1 problemNo problemsLn 13, Col 52
WHAT EXCEL RECEIVES
D2fx=ROUND(B2*C2,2)
A
B
C
D
1
2
3
4
5
6
Customer
Qty
Unit price
Total
Halvorsen Freight
12
480.00
5,760.00
Okapi Health
4
1,250.00
5,000.00
Brightwater Labs
30
96.50
2,895.00
Tanaka Robotics
8
720.00
5,760.00
Total
19,415.00
tsc failed. No file was written.

One schema.
Every report.

Pick columns per audience, generate columns from runtime data, switch to a native Excel table, change the theme. The schema stays one file.

Click the highlighted values0 edits
const deals = createExcelSchema<Deal, Ctx>({  mode: ,})  .column("account", { accessor: "account" })  .column("owner", { accessor: "owner.name" })  .column("stage", { type: "badge", accessor: "stage" })  .dynamic("regions", (b, { ctx }) =>    ctx.regions.forEach((r) =>      b.column(r, { accessor: (d) => d.byRegion[r] })))  .column("total", {    accessor: "total",    summary: (s) => [s.formula("sum")],  })  .build() workbook.sheet("Deals").table("deals", {  title: "Q3 deals by region",  rows, schema: deals,  context: { regions: [, , , ] },  // audience     // no select: every column ships  theme: ,})
F9fx=SUM(F3:F8)
A
B
C
D
E
F
1
2
3
4
5
6
7
8
9
Q3 deals by region
Account
Owner
Stage
EMEA
NA
Total
Halvorsen Freight
Ama Okafor
Won
$48,200
$12,400
$70,400
Okapi Health
Léa Moreau
Proposal
$0
$36,500
$48,600
Tanaka Robotics
Jonas Lindqvist
Won
$6,300
$0
$65,000
Quarry Lane Studio
Sami Haddad
Negotiation
$14,100
$22,900
$48,300
Nordvik Energy
Jonas Lindqvist
Won
$52,600
$0
$52,600
Brightwater Labs
Sami Haddad
Proposal
$0
$8,400
$11,500
TOTAL
$121,200
$80,200
$296,400
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DealsNotes+
Rendered from deals.report.ts6 columns, report, forest

Real workbooks.
Open any of them.

Every example in the playground is generated by typed-xlsx, with the schema that produced it right next to the file.

Executive Board Pack workbook with grouped commercial and adoption headers

Executive Board Pack

Two stacked tables per sheet, grouped headers, portfolio totals and averages.

Open in playground
Streaming fulfillment export with thousands of shipment rows and totals

Streaming Fulfillment Export

Thousands of shipment rows committed in batches, with totals and a fill-rate average at the end.

Open in playground
Deal desk quote review with multi-line quotes expanded into sub-rows

Deal Desk Quote Review

Multi-line quotes expanded into sub-rows, with margin math and approval flags.

Open in playground
Renewal operations workbook with editable inputs and protected logic

Renewal Ops Workbook

Unlocked inputs with validation, while formulas and logic columns stay protected.

Open in playground

Zero dependencies.
Its own XLSX engine.

No SheetJS, no ExcelJS. typed-xlsx writes the OOXML parts and the ZIP container itself, so there is nothing else to audit or patch.

$npm i typed-xlsx
Copyright © 2026 Cyprien Thao. Released under the MIT License.