Defining Columns
A schema is a chain of .column(id, options) calls on createExcelSchema<Row>(), closed with .build().
import { createExcelSchema } from "typed-xlsx";
type User = { name: string; email: string; role: string };
const users = createExcelSchema<User>()
.column("name", { header: "Name", accessor: "name" })
.column("email", { header: "Email", accessor: "email" })
.column("role", { header: "Role", accessor: "role" })
.build();
The column ID is how the rest of the API refers to the column: formulas, select, summaries. It must be unique in the schema; a duplicate throws as soon as it's declared. The header defaults to the ID.
The built schema is a plain, immutable object. Export it from a module and reuse it across workbooks.
From row to cell
A cell's value goes through up to three steps, then gets its number format and style:
- 01Accessor
accessora field path or a function of the row - 02Transform
transformoptional, receives the value and the row - 03Default
defaultValueused when the value is null or undefined - 04Cell
format · stylenumber format and look
Accessors
A string accessor is a path into the row. typed-xlsx autocompletes every valid path, nested ones included, and rejects the rest:
import { createExcelSchema } from "typed-xlsx";
type Row = { user: { firstName: string }; company: { name: string } };
createExcelSchema<Row>()
.column("firstName", { accessor: "user.firstName" })
.column("company", { accessor: "company.title" })
.build();
A function accessor derives the value from the whole row:
import { createExcelSchema } from "typed-xlsx";
type Employee = { firstName: string; lastName: string; hiredAt: string };
const schema = createExcelSchema<Employee>()
.column("fullName", {
header: "Employee",
accessor: (row) => `${row.firstName} ${row.lastName}`,
})
.column("hired", { header: "Hired", accessor: (row) => new Date(row.hiredAt) })
.build();
An accessor that returns an array expands the row into several worksheet rows. See Row Model.
Transform and default value
transform runs after the accessor and receives the typed value. defaultValue fills in when the result is null or undefined.
import { createExcelSchema } from "typed-xlsx";
type Account = { plan: "starter" | "pro" | "enterprise"; department: string | null };
const labels = { starter: "Starter", pro: "Pro", enterprise: "Enterprise" } as const;
const schema = createExcelSchema<Account>()
.column("plan", {
accessor: "plan",
transform: ({ value }) => labels[value],
})
.column("department", { accessor: "department", defaultValue: "Unassigned" })
.build();
Widths
By default each column is as wide as its longest value. Bound it, or fix it:
import { createExcelSchema } from "typed-xlsx";
const schema = createExcelSchema<{ description: string; amount: number }>()
.column("description", {
accessor: "description",
minWidth: 20,
maxWidth: 60,
style: { alignment: { wrapText: true, vertical: "top" } },
})
.column("amount", { accessor: "amount", width: 14, style: { numFmt: '"$"#,##0.00' } })
.build();
Widths are in Excel character units. A fixed width wins over everything; otherwise the measured width is clamped between minWidth and maxWidth. Measuring ignores wrapping, so give wrapped text a maxWidth, or the column grows to fit its longest paragraph on one line.
Columns that depend on the export
condition includes or drops a column based on the schema context. It runs once per table, not per row.
import { createExcelSchema, createWorkbook } from "typed-xlsx";
type Deal = { owner: string; amount: number; margin: number };
const schema = createExcelSchema<Deal, { showFinance: boolean }>()
.column("owner", { accessor: "owner" })
.column("amount", { accessor: "amount", condition: ({ ctx }) => ctx.showFinance })
.column("margin", { accessor: "margin", condition: ({ ctx }) => ctx.showFinance })
.build();
createWorkbook()
.sheet("Deals")
.table("deals", { rows: [], schema, context: { showFinance: false } });
To choose columns per export without a context flag, use Column Selection.
Cell types
type turns a column into something other than plain values:
type | Renders | Page |
|---|---|---|
"badge" | a styled status pill per value | Badges and Checkboxes |
"checkbox" | a native Excel checkbox | Badges and Checkboxes |
"sparkline" | a small line or column chart in the cell | Sparklines |
"image" | an embedded picture or an IMAGE() URL | Images |
Plain columns can also carry a hyperlink or a validation dropdown.
Column options
| Option | Type | Purpose |
|---|---|---|
header | string | ({ ctx }) => string | header text, defaults to the column ID |
accessor | path or (row) => value | where the value comes from |
transform | ({ value, row, ctx }) => value | reshape the value |
defaultValue | cell value | used for null and undefined |
formula | ({ refs, row, fx, ctx }) => … | write an Excel formula instead of a value, see Formula Columns |
format | number format, or a function of the row and context | shorthand for style.numFmt |
style | CellStyle or ({ row, ctx }) => CellStyle | static or per-row style |
headerStyle | CellStyle | style of the header cell |
conditionalStyle | builder | native conditional formats |
summary | (s) => [...] | summary rows, report mode |
totalsRow | { function } or { label } | totals row, excel-table mode |
validation | builder | data validation |
hyperlink | target or function | hyperlinks |
expansion | "auto" | "single" | "expand" | where a formula is written on expanded rows, see Row Model |
width | number | fixed width |
minWidth, maxWidth | number | bounds for the measured width |
autoWidth | boolean, default true | measure the content |
condition | ({ ctx }) => boolean | include the column for this export or not |
type | "badge" | "checkbox" | "sparkline" | "image" | a cell type |