Value Formatting
Column format can be a structured FormatSpec (thread-safe; works on all paths) or a function (executed on main-thread paths; stripped on the browser worker path — see "Function form" below).
FormatSpec
| Type | Options | Example | Description |
|---|---|---|---|
enum | map, fallback? | { type: "enum", map: { paid: "Paid" }, fallback: "Unknown" } | Maps raw values to labels; unmapped values use fallback or pass through |
date | pattern? (default yyyy-MM-dd) | { type: "date" } | Converts to an Excel date serial and auto-injects numFormat |
datetime | pattern? (default yyyy-MM-dd HH:mm) | { type: "datetime" } | Same, with time |
number | decimals? (default 0), thousands? | { type: "number", decimals: 2, thousands: true } | Numeric semantics; the Workbook path keeps full precision rendered via numFormat |
padding | fill, length, align? (left/right) | { type: "padding", fill: "0", length: 6, align: "left" } | Pads to a fixed length (IDs, codes) |
columns: [
{ prop: "orderId", label: "Order ID", width: 12 },
{
prop: "date",
label: "Date",
width: 12,
format: { type: "date", pattern: "yyyy/MM/dd" },
},
{
prop: "amount",
label: "Amount",
width: 14,
format: { type: "number", decimals: 2, thousands: true },
},
{
prop: "status",
label: "Status",
width: 10,
format: { type: "enum", map: { paid: "Paid" }, fallback: "Unknown" },
},
{
prop: "code",
label: "Code",
width: 12,
format: { type: "padding", fill: "0", length: 6, align: "right" },
},
];Function form
{
prop: "amount",
label: "Amount",
width: 14,
format: (value, row) => {
const n = Number(value);
return n >= 1000 ? `Large ${n.toFixed(2)}` : n.toFixed(2);
},
}Signature: (value: unknown, row: Record<string, unknown>) => string | number | boolean. Functions cannot cross the structured-clone boundary, so behavior differs per path: the main path (browser < 20,000 rows / Node < 50,000 rows), Node's stream path (≥ 50,000 rows, also main-thread) and the main-thread retry after a worker failure (the original options keep the function — only the copy sent to the worker is stripped) execute them normally; the browser worker path (auto ≥ 20,000 rows, or explicit mode: "worker" / mode: "stream") strips them with a console.warn and exports the raw value (no error, no fallback to main). Convert to FormatSpec to keep formatting on the worker path.
Cross-path precision notes
Behavior differs slightly between paths — always set decimals explicitly:
- Workbook path (main / worker+workbook): full precision is preserved; display decimals come from the auto-injected
numFormat; - Stream path (≥ 50,000 rows): no
numFormatsupport;decimalsare baked into the stored value (9999.99→10000); - So without explicit
decimals(default 0), the stored cell value can differ between paths — explicitdecimalsis the main way to guarantee cross-threshold consistency.
Dates
date / datetime accept Date objects, parseable strings or timestamps. The Workbook path writes a serial + numFormat; Stream path (no numFormat support) emit readable strings per the pattern (mm resolves to minutes vs month by its context).
Pattern tokens (differ between paths): the stream path (≥ 50,000 rows, explicit mode: "stream", or the fallback) parses only the six tokens yyyy / MM / dd / HH / mm / ss (case-insensitive). The Workbook path hands the pattern to Excel as a numFormat, where any valid format code also renders (yy, single-letter m/d, AM/PM, quoted literals like yyyy"年"). Characters outside the six tokens are emitted as-is after lower-casing on the stream path — pattern: "yy-MM-dd" exports yy-01-05 above the threshold and a proper two-digit year below it. Two consequences worth knowing: the lower-casing also hits quoted literals (so yyyy-MM-dd"T"HH:mm renders 2026-07-01"t"15:30 above the threshold), and the quotes themselves are emitted, since the stream path does not interpret them (yyyy"年"M"月"d"日" renders 2026"年"m"月"d"日" — M/d are not tokens there). Superset tokens are likewise only partially passed through: their six-token prefix still parses and the remainder leaks out ("mmm" → "09m", while Excel renders the month abbreviation). Stick to the six tokens for cross-threshold consistency, and prefer numFormat (a style, dropped wholesale on the stream path) over pattern when you need quoted literals.
Timezone convention (consistent across paths): date / datetime interpret and render values by their UTC components — the workbook serial comes from modern-xlsx's dateToSerial (UTC wall clock) and the stream strings use the same UTC components, so one input renders identically on every path in every timezone. ISO date strings ("2026-07-01") parse as UTC midnight per ECMA-262 and fit this convention natively. Note that Dates constructed with local time (new Date(year, month, day)) carry UTC components that can fall on the previous day in non-UTC timezones (local midnight in UTC+8 = 16:00 UTC the day before). Prefer ISO strings or Date.UTC(...) for timezone-stable date columns.