typed-xlsx
Columns

Defining Columns

Column IDs, typed accessors, transforms, default values, widths, conditional columns, and the full list of column options.

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:

  1. 01Accessoraccessora field path or a function of the row
  2. 02Transformtransformoptional, receives the value and the row
  3. 03DefaultdefaultValueused when the value is null or undefined
  4. 04Cellformat · 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" })
Type '"company.title"' is not assignable to type '"user.firstName" | "company.name"'.
.
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:

typeRendersPage
"badge"a styled status pill per valueBadges and Checkboxes
"checkbox"a native Excel checkboxBadges and Checkboxes
"sparkline"a small line or column chart in the cellSparklines
"image"an embedded picture or an IMAGE() URLImages

Plain columns can also carry a hyperlink or a validation dropdown.

Column options

OptionTypePurpose
headerstring | ({ ctx }) => stringheader text, defaults to the column ID
accessorpath or (row) => valuewhere the value comes from
transform({ value, row, ctx }) => valuereshape the value
defaultValuecell valueused for null and undefined
formula({ refs, row, fx, ctx }) => …write an Excel formula instead of a value, see Formula Columns
formatnumber format, or a function of the row and contextshorthand for style.numFmt
styleCellStyle or ({ row, ctx }) => CellStylestatic or per-row style
headerStyleCellStylestyle of the header cell
conditionalStylebuildernative conditional formats
summary(s) => [...]summary rows, report mode
totalsRow{ function } or { label }totals row, excel-table mode
validationbuilderdata validation
hyperlinktarget or functionhyperlinks
expansion"auto" | "single" | "expand"where a formula is written on expanded rows, see Row Model
widthnumberfixed width
minWidth, maxWidthnumberbounds for the measured width
autoWidthboolean, default truemeasure the content
condition({ ctx }) => booleaninclude the column for this export or not
type"badge" | "checkbox" | "sparkline" | "image"a cell type
Copyright © 2026 Cyprien Thao. Released under the MIT License.