Skip to content

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 ​

TypeOptionsExampleDescription
enummap, fallback?{ type: "enum", map: { paid: "Paid" }, fallback: "Unknown" }Maps raw values to labels; unmapped values use fallback or pass through
datepattern? (default yyyy-MM-dd){ type: "date" }Converts to an Excel date serial and auto-injects numFormat
datetimepattern? (default yyyy-MM-dd HH:mm){ type: "datetime" }Same, with time
numberdecimals? (default 0), thousands?{ type: "number", decimals: 2, thousands: true }Numeric semantics; the Workbook path keeps full precision rendered via numFormat
paddingfill, length, align? (left/right){ type: "padding", fill: "0", length: 6, align: "left" }Pads to a fixed length (IDs, codes)
ts
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 ​

ts
{
  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 numFormat support; decimals are baked into the stored value (9999.99 → 10000);
  • So without explicit decimals (default 0), the stored cell value can differ between paths — explicit decimals is 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.