# @office-kit/xlsx
> Read and write Excel `.xlsx` workbooks from Node 22+ and modern browsers, with no Python or native runtime dependencies. Includes a streaming writer (10M rows in fixed memory) and a SAX-based row iterator for huge sheets.
This file is written for AI assistants. It is self-contained: an agent can use
every documented feature of @office-kit/xlsx from this page alone. The link index at
the bottom points at canonical docs — append `.md` to any docs URL for raw
Markdown (e.g. `/docs/install.md`), or fetch `/llms-full.txt` for every
page concatenated into a single document.
## Runtime
- Node `>=22` (uses built-in Web Streams, Blob, fetch).
- Modern browsers, Bun, Deno, Cloudflare Workers, edge runtimes — anywhere
with `fetch` + Web Streams.
- ESM-only. `"sideEffects": false` — fully tree-shakable.
- No runtime dependencies on Python or native binaries. Internally uses
`fflate` (deflate/inflate), `saxes` (SAX XML), `fast-xml-parser`.
## Install
```sh
pnpm add @office-kit/xlsx
# or: npm install @office-kit/xlsx / bun add @office-kit/xlsx
```
TypeScript: `tsconfig.json` should use `"moduleResolution": "bundler"`
(or `"node16"` / `"nodenext"`) so subpath entries resolve.
## One way to do one thing
@office-kit/xlsx has **no root barrel export**. `import "@office-kit/xlsx"` is intentionally
not supported. Every export lives behind a section subpath, and each public
function has exactly one canonical import path — once you know the function
name, you know where to import it from.
| Subpath | What lives here |
|------------------------|------------------------------------------------------------------------------------------------------------|
| `@office-kit/xlsx/io` | `loadWorkbook`, `saveWorkbook`, `workbookToBytes`; browser-safe `fromArrayBuffer` / `fromBlob` / `fromResponse` / `fromStream` / `toArrayBuffer` / `toBlob`; types `XlsxSource`, `XlsxSink`, `LoadOptions`, `SaveOptions` |
| `@office-kit/xlsx/node` | Node-fs bridges: `fromFile`, `fromFileSync`, `fromBuffer`, `fromReadable`, `toFile`, `toWritable`, `toBuffer`, `workbookToBuffer` |
| `@office-kit/xlsx/streaming` | `loadWorkbookStream`, `createWriteOnlyWorkbook` + their type surface (`ReadOnlyWorkbook`, `ReadOnlyWorksheet`, `ReadOnlyCell`, `IterRowsOptions`, `LoadWorkbookStreamOptions`, `WriteOnlyWorkbook`, `WriteOnlyWorksheet`, `WriteOnlyOptions`, `WriteOnlyRowItem`, `WriteOnlyStyle`) |
| `@office-kit/xlsx/workbook` | `createWorkbook`, `addWorksheet`, `addChartsheet`, `addDefinedName`, `makeWorkbookProtection`, `makeWorkbookView` / `makeCustomWorkbookView`, `makeCalcProperties`, `makeWorkbookProperties`, `makeFileVersion`, `makeFileSharing`, `makeFileRecoveryProperties`, `makeSmartTagProperties` / `makeSmartTagType`, `makeFunctionGroup` / `makeFunctionGroups` |
| `@office-kit/xlsx/worksheet` | `setCell`, `setCellByCoord`, `getCell`, `getCellByCoord`, `getCellsInRow`, `getCellsInColumn`, `getCellsInRange`, `appendRow`, `appendRows`, `iterRows`, `iterValues`, `getMaxRow`, `getMaxCol`, `mergeCells` / `unmergeCells` / `unmergeCellsAt`, `makeSheetView` / `makeFreezePane` / `freezePaneRef`, `setHyperlink` / `makeHyperlink` / `getCellHyperlink`, `addExcelTable` / `makeTableDefinition` / `makeTableColumn`, `addAutoFilter` / `addAutoFilterColumn` / `makeAutoFilter` / `makeFilterColumn`, `makeDataValidation` / `addDataValidation`, `makeCfRule` / `makeConditionalFormatting` / `addConditionalFormatting`, `makeLegacyComment` / `getCellComment`, `makeColumnDimension` / `makeRowDimension`, `makeSheetProtection` / `makeProtectedRange`, `makeSortState` / `makeSortCondition`, `makeFormControl` / `makeOleObject`, `makeCustomSheetView`, `makeCellWatch` / `makeIgnoredError`, `makeSheetProperties` |
| `@office-kit/xlsx/cell` | `makeCell`, `getCoordinate`, `setCellValue`, `bindValue`, `setFormula` / `setSharedFormula` / `setArrayFormula` / `setDataTableFormula`, `makeErrorValue` / `makeDurationValue`, `makeRichText` / `makeTextRun` / `richTextToString`, predicates (`isFormulaCell`, `isRichTextCell`, `isEmptyCell`, `isMergedCell`, `isErrorCell`, `isFormulaValue`, `isRichTextValue`, `isErrorValue`, `isDurationValue`), and value coercers (`cellValueAsString`, `cellValueAsBoolean`, `cellValueAsDate`, `cellValueAsNumber`, `cellValueAsPrimitive`); types `Cell`, `CellValue`, `MergedCell`, `FormulaValue`, `RichText`, `TextRun`, `InlineFont`, `ExcelErrorCode` |
| `@office-kit/xlsx/styles` | Per-cell: `setBold`, `setFontSize`, `setFontName`, `setFontColor`, `setCellFont`, `setCellFill`, `setCellBorder` / `setCellBorderAll`, `setCellAlignment` / `centerCell`, `setCellProtection`, `setCellNumberFormat`, `setCellStyle`, `setCellBackgroundColor`, `setCellAsCurrency`, `setCellAsPercent`, `setCellAsDate`, `setCellAsNumber`. Range-wide `setRange*` variants for the same axes. Built-in format constants `FORMAT_GENERAL`, `FORMAT_TEXT`, `FORMAT_NUMBER`, `FORMAT_NUMBER_00`, `FORMAT_PERCENTAGE`, `FORMAT_PERCENTAGE_00`, `FORMAT_DATE_DATETIME`, etc. Named-style + DXF (differential format) APIs |
| `@office-kit/xlsx/chart` | Legacy `c:` chart kinds — `makeBarChart`, `makeLineChart`, `makeAreaChart`, `makePieChart`, `makeDoughnutChart`, `makeScatterChart`, `makeRadarChart`, `makeBubbleChart`, `makeStockChart`, `makeSurfaceChart`, `makeOfPieChart`. Series builder `makeBarSeries`. Top-level wrapper `makeChartSpace`. Modern `cx:` chartex kinds — `makeSunburstChart`, `makeTreemapChart`, `makeWaterfallChart`, `makeHistogramChart`, `makeParetoChart`, `makeFunnelChart`, `makeBoxWhiskerChart`, `makeRegionMapChart` |
| `@office-kit/xlsx/chartsheet` | Standalone chartsheets (`addChartsheet` lives on `@office-kit/xlsx/workbook`; chartsheet shape and helpers live here) |
| `@office-kit/xlsx/drawing` | `addImageAt`, `addChartAt`, `loadImage`, `detectImageFormat`, `detectImageDimensions`; `makeAbsoluteAnchor`, `makeOneCellAnchor`, `makeTwoCellAnchor`, `anchorMarkerFromCellRef`; `makeDrawing`, `makePictureDrawingItem`, `makeChartDrawingItem`, `listImagesOnSheet`, `listChartsOnSheet`, `removeAllImages`, `removeAllCharts`, `removeAllDrawingItems`; DML preset geometry / effect helpers |
| `@office-kit/xlsx/utils` | A1 ↔ row/col conversion (`coordinateFromString`, `coordinateToTuple`, `tupleToCoordinate`, `columnLetterFromIndex`, `columnIndexFromLetter`, `rangeBoundaries`, `boundariesToRangeString`, `parseSheetRange`, `formatSheetQualifiedRef`, validators `isValidCellRef` / `isValidRangeRef` / `isValidColumnLetter` / `isValidColumnNumber` / `isValidRowNumber`); Excel date helpers (`excelToDate`, `dateToExcel`, `excelToDuration`, `durationToExcel`, `toIso8601`, `fromIso8601`); EMU/pixel/point unit conversion; `escapeCellString` / `unescapeCellString`; `inferCellType` + `ERROR_CODES`; error hierarchy `OpenXmlError`, `OpenXmlIoError`, `OpenXmlSchemaError`, `OpenXmlDecompressionBombError`, `OpenXmlInvalidWorkbookError`, `OpenXmlNotImplementedError` |
| `@office-kit/xlsx/packaging` | Low-level OPC (Open Packaging Conventions) parts — escape hatch only |
| `@office-kit/xlsx/xml` | Hardened XML reader/writer — escape hatch only |
| `@office-kit/xlsx/zip` | ZIP reader/writer with decompression-bomb defense — escape hatch only |
| `@office-kit/xlsx/schema` | OOXML namespace and ECMA-376 type constants — escape hatch only |
The four "escape hatch" subpaths (`packaging`, `xml`, `zip`, `schema`)
are only needed when extending the library. Anything an end-user wants to do
should be reachable through the higher-level subpaths above them.
Bundle budgets, min + brotli:
- `@office-kit/xlsx/io` plus the rest of the high-level surface: ≤ 120 KB (currently ~78 KB)
- `@office-kit/xlsx/streaming`: ≤ 80 KB (currently ~47 KB)
## Coordinates and cell values
- Cell coordinates are **1-indexed**. `row=1, col=1` corresponds to `A1`.
Matches the openpyxl convention.
- `CellValue` is a tagged union:
```ts
type CellValue =
| number
| string
| boolean
| Date
| { kind: 'duration'; ms: number }
| { kind: 'error'; code: ExcelErrorCode } // '#DIV/0!' | '#N/A' | '#NAME?' | '#NULL!' | '#NUM!' | '#REF!' | '#VALUE!' | '#GETTING_DATA' | '#SPILL!' | '#CALC!'
| { kind: 'rich-text'; runs: RichText }
| FormulaValue // { kind: 'formula'; formula: string; cachedValue?: number | string | boolean; ... }
| null; // empty cell
```
- `setCell(sheet, row, col, value)` returns the `Cell` it created, so you
can chain it into `setFormula`, `setBold`, `setCellNumberFormat`, etc.
- For A1-style addressing, prefer `setCellByCoord(ws, 'A1', value)` /
`getCellByCoord(ws, 'A1')`.
- To convert between A1 and row/col, use the helpers in `@office-kit/xlsx/utils`
(`coordinateFromString`, `coordinateToTuple`, `columnLetterFromIndex`,
`columnIndexFromLetter`).
## Loading a workbook
`loadWorkbook` accepts an `XlsxSource` from `@office-kit/xlsx/io` or
`@office-kit/xlsx/node`. Pick the helper that matches your byte source:
```ts
import { loadWorkbook } from '@office-kit/xlsx/io';
// Node, from file path (streamed via fs.ReadStream)
import { fromFile } from '@office-kit/xlsx/node';
const wb = await loadWorkbook(fromFile('in.xlsx'));
// Node, from Buffer / Uint8Array
import { fromBuffer } from '@office-kit/xlsx/node';
const wb = await loadWorkbook(fromBuffer(buf));
// Node, from a Readable
import { fromReadable } from '@office-kit/xlsx/node';
const wb = await loadWorkbook(fromReadable(stream));
// Browser, from fetch (streamed — parsing starts before download finishes)
import { fromResponse } from '@office-kit/xlsx/io';
const wb = await loadWorkbook(fromResponse(await fetch('/sheet.xlsx')));
// Browser, from / drag-and-drop
import { fromBlob } from '@office-kit/xlsx/io';
const wb = await loadWorkbook(fromBlob(file));
// Anywhere, from ArrayBuffer / ReadableStream
import { fromArrayBuffer, fromStream } from '@office-kit/xlsx/io';
const wb = await loadWorkbook(fromArrayBuffer(ab));
const wb2 = await loadWorkbook(fromStream(readable));
```
`wb.sheets` is an array of *discriminated unions* — `SheetRef` objects
with a `kind` of `'worksheet'` or `'chartsheet'`. Narrow on `kind`
before reading `.sheet`:
```ts
for (const ref of wb.sheets) {
if (ref.kind !== 'worksheet') continue;
const ws = ref.sheet; // Worksheet
console.log(ref.name); // sheet name (also ws.title)
// ...
}
```
Iterate cells with the canonical helpers from `@office-kit/xlsx/worksheet`:
```ts
import { iterRows, iterValues } from '@office-kit/xlsx/worksheet';
for (const row of iterRows(ws)) { // Cell[] per row
for (const cell of row) console.log(cell.row, cell.col, cell.value);
}
const grid = [...iterValues(ws)]; // CellValue[][]
```
`loadWorkbook` options (`LoadOptions`) cover decompression limits, shared
string interning, calc-property handling, and which parts to skip. The
defaults are safe; only override them when you have a reason.
## Saving a workbook
```ts
import { saveWorkbook, workbookToBytes, toBlob, toArrayBuffer } from '@office-kit/xlsx/io';
import { toFile, toWritable, workbookToBuffer } from '@office-kit/xlsx/node';
// Node, to file path (streamed via fs.WriteStream)
await saveWorkbook(wb, toFile('out.xlsx'));
// Node, to a Writable
await saveWorkbook(wb, toWritable(stream));
// Node, to a Buffer
const buf: Buffer = await workbookToBuffer(wb);
// Anywhere, to Uint8Array / Blob / ArrayBuffer
const bytes = await workbookToBytes(wb);
const blob = await toBlob(wb);
const ab = await toArrayBuffer(wb);
// Triggering a browser download:
const url = URL.createObjectURL(await toBlob(wb));
const a = document.createElement('a');
a.href = url; a.download = 'report.xlsx'; a.click();
URL.revokeObjectURL(url);
```
## Building a workbook from scratch
```ts
import { workbookToBytes } from '@office-kit/xlsx/io';
import { createWorkbook, addWorksheet, addDefinedName } from '@office-kit/xlsx/workbook';
import { setCell, mergeCells, makeFreezePane, makeSheetView } from '@office-kit/xlsx/worksheet';
import { setFormula } from '@office-kit/xlsx/cell';
import { setBold, centerCell } from '@office-kit/xlsx/styles';
const wb = createWorkbook();
const ws = addWorksheet(wb, 'Sales');
// Header
const title = setCell(ws, 1, 1, 'Q2 sales');
setBold(wb, title);
centerCell(wb, title);
mergeCells(ws, 'A1:C1');
// Body
setCell(ws, 2, 1, 'Region');
setCell(ws, 2, 2, 'Revenue');
setCell(ws, 3, 1, 'NA');
setCell(ws, 3, 2, 12_400);
setCell(ws, 4, 1, 'EU');
setCell(ws, 4, 2, 9_800);
setFormula(setCell(ws, 5, 2, null), 'SUM(B3:B4)', { cachedValue: 22_200 });
// Freeze header
ws.views.push(makeSheetView({ pane: makeFreezePane('A3') }));
// Cross-sheet reference
addDefinedName(wb, { name: 'TotalRevenue', value: 'Sales!$B$5' });
const bytes = await workbookToBytes(wb);
```
## Editing an existing workbook
```ts
import { loadWorkbook, saveWorkbook } from '@office-kit/xlsx/io';
import { fromFile, toFile } from '@office-kit/xlsx/node';
import { setCell } from '@office-kit/xlsx/worksheet';
const wb = await loadWorkbook(fromFile('in.xlsx'));
const ref = wb.sheets[0];
if (ref?.kind === 'worksheet') setCell(ref.sheet, 1, 1, 'updated');
await saveWorkbook(wb, toFile('out.xlsx'));
```
## Streaming read (huge sheets, fixed memory)
```ts
import { fromFile } from '@office-kit/xlsx/node';
import { loadWorkbookStream } from '@office-kit/xlsx/streaming';
const wb = await loadWorkbookStream(fromFile('big.xlsx'));
const name = wb.sheetNames[0] ?? ''; // workbook part is parsed eagerly
const sheet = wb.openWorksheet(name);
// Walk every row.
for await (const row of sheet.iterRows()) {
console.log(row.map((c) => c.value));
}
// Or bound the walk. minRow is tag-scanned — a 1M-row skip does not parse
// the skipped rows, so this is fast even on multi-million-row sheets.
for await (const row of sheet.iterRows({ minRow: 1_000_000, maxRow: 1_000_100, minCol: 1, maxCol: 8 })) {
// ...
}
await wb.close();
```
Each yielded row is a `ReadOnlyCell[]`. `cell.value` is
`string | number | boolean | null`; `cell.coordinate` is the A1 string.
## Streaming write (huge sheets, fixed memory)
```ts
import { toFile } from '@office-kit/xlsx/node';
import { createWriteOnlyWorkbook } from '@office-kit/xlsx/streaming';
const wb = await createWriteOnlyWorkbook(toFile('big.xlsx'));
const ws = await wb.addWorksheet('Data');
// Column widths MUST be set before the first appendRow — once any row is
// written, the worksheet's block is locked.
ws.setColumnWidth(1, 24);
ws.setColumnWidth(2, 12);
for (let r = 0; r < 10_000_000; r++) {
await ws.appendRow([r, `row-${r}`, r * Math.PI]);
}
await ws.close(); // REQUIRED — finalizes this sheet's .
await wb.finalize(); // REQUIRED — writes the OPC central directory.
```
Streaming-write constraints:
- Column widths and column-level styling must be set **before** the first
`appendRow`. Once any row is written, `` is locked.
- No random-access edits — once a row is appended, you cannot go back and
change a cell.
- Both `ws.close()` and `wb.finalize()` are required; skipping either
leaves a truncated zip.
- Charts, drawings, and Excel tables are not supported in the streaming
writer. If you need them, build the workbook in memory.
For a 10M-row × 3-column workbook on commodity hardware: ~30s, ~75 MB peak
heap, ~110 MB on disk.
## Styling cells
Per-cell helpers (apply to a `Cell` returned by `setCell` / `getCell`):
```ts
import {
setBold, setFontSize, setFontName, setFontColor,
setCellBackgroundColor, centerCell, setCellBorderAll,
setCellAsCurrency, setCellAsPercent, setCellAsDate, setCellAsNumber,
setCellNumberFormat, setCellStyle,
FORMAT_DATE_DATETIME, FORMAT_PERCENTAGE, FORMAT_NUMBER_00,
} from '@office-kit/xlsx/styles';
const c = setCell(ws, 1, 1, 'Header');
setBold(wb, c);
setFontSize(wb, c, 14);
setFontName(wb, c, 'Inter');
setFontColor(wb, c, 'FFFFFFFF');
setCellBackgroundColor(wb, c, 'FF3366CC'); // AARRGGBB (leading FF = opaque)
centerCell(wb, c);
setCellBorderAll(wb, c, { style: 'thin' });
// Number formats
setCellAsCurrency(wb, setCell(ws, 2, 1, 1234.5), { symbol: '$' });
setCellAsPercent(wb, setCell(ws, 2, 2, 0.125), 1);
setCellAsDate(wb, setCell(ws, 2, 3, new Date()), 'yyyy-mm-dd');
setCellAsNumber(wb, setCell(ws, 2, 4, 42_000), 0);
setCellNumberFormat(wb, setCell(ws, 2, 5, new Date()), FORMAT_DATE_DATETIME);
// Atomic apply of multiple aspects in one styleId update:
setCellStyle(wb, c, {
font: { bold: true, size: 12, color: { rgb: 'FFFFFFFF' } },
fill: { patternType: 'solid', fgColor: { rgb: 'FF3366CC' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { top: { style: 'thin' }, bottom: { style: 'thin' } },
numberFormat: FORMAT_NUMBER_00,
});
```
For range-wide styling, use the matching `setRange*` helpers in
`@office-kit/xlsx/styles` (e.g. `setRangeFont`, `setRangeAlignment`,
`setRangeBorderBox`, `setRangeNumberFormat`).
Colors throughout the library are hex `AARRGGBB` strings. Leading `FF`
means opaque; `80` would be 50% alpha.
## Formulas
```ts
import { setFormula, setSharedFormula, setArrayFormula } from '@office-kit/xlsx/cell';
import { setCell } from '@office-kit/xlsx/worksheet';
// Plain formula with optional cached value
setFormula(setCell(ws, 3, 1, null), 'SUM(A1:A2)', { cachedValue: 42 });
// Array formula (CSE — host range describes the spill area)
setArrayFormula(setCell(ws, 1, 3, null), 'TRANSPOSE(A1:A3)', { ref: 'C1:C3' });
// Shared formula (Excel optimization for runs of similar formulas)
setSharedFormula(setCell(ws, 2, 1, null), '=A1*2', { si: 0, master: true, ref: 'A2:A10' });
```
`cachedValue` is optional. Excel will recalc on open anyway, but providing
it keeps the file viewable in tools that don't recalculate (Quick Look,
preview generators, headless readers).
## Rich text (multi-format cell content)
```ts
import { makeRichText, makeTextRun } from '@office-kit/xlsx/cell';
import { setCell } from '@office-kit/xlsx/worksheet';
const rich = makeRichText([
makeTextRun('Hello, ', { bold: true }),
makeTextRun('world', { italic: true, color: { rgb: 'FFFF0000' } }),
]);
setCell(ws, 1, 1, { kind: 'rich-text', runs: rich });
```
## Hyperlinks
```ts
import { setHyperlink } from '@office-kit/xlsx/worksheet';
setHyperlink(ws, 'A1', {
target: 'https://example.com',
display: 'Open', // tooltip / fallback display text
tooltip: 'Open example.com',
});
```
Hyperlinks live separately from cell values. The cell holds the visible
text via `setCell`; `setHyperlink` wires up the URL underneath.
## Merge and freeze
```ts
import { mergeCells, unmergeCells, makeFreezePane, makeSheetView } from '@office-kit/xlsx/worksheet';
mergeCells(ws, 'A1:C1'); // accepts string or { from, to } range
ws.views.push(makeSheetView({ pane: makeFreezePane('A2') })); // freeze row 1
ws.views.push(makeSheetView({ pane: makeFreezePane('B1') })); // freeze col A
ws.views.push(makeSheetView({ pane: makeFreezePane('B2') })); // freeze both
```
## Excel Tables, AutoFilter, Data Validation, Conditional Formatting
```ts
import {
addExcelTable, addAutoFilter,
makeDataValidation, addDataValidation,
makeCfRule, makeConditionalFormatting, addConditionalFormatting,
} from '@office-kit/xlsx/worksheet';
// Excel Table — named range with banded styling and a filter dropdown on
// every header. Use the higher-level addExcelTable; for full control,
// makeTableDefinition + ws.tables.set(...).
addExcelTable(wb, ws, {
name: 'Items',
ref: 'A1:C4',
columns: ['SKU', 'Name', 'Price'],
style: 'TableStyleMedium2',
});
// Just an autoFilter (no banded styling)
addAutoFilter(ws, 'A1:C1');
// Dropdown data validation
addDataValidation(ws, makeDataValidation({
type: 'list',
sqref: 'B2:B100',
formula1: '"Open,In progress,Closed"',
prompt: 'Pick a status',
errorTitle: 'Invalid value',
error: 'Pick one of the listed values.',
}));
// 3-color scale heat-map
addConditionalFormatting(ws, makeConditionalFormatting({
sqref: 'A1:A10',
rules: [makeCfRule({
type: 'colorScale',
priority: 1,
formulas: [],
innerXml:
'' +
'' +
'' +
'',
})],
}));
```
## Charts and images
```ts
import { makeBarChart, makeBarSeries, makeChartSpace } from '@office-kit/xlsx/chart';
import { addChartAt, addImageAt, loadImage } from '@office-kit/xlsx/drawing';
import { readFile } from 'node:fs/promises';
// Bar / column chart anchored to D2.
const chart = makeBarChart({
barDir: 'col',
grouping: 'clustered',
series: [
makeBarSeries({
idx: 0,
tx: { kind: 'literal', value: 'Revenue' },
cat: { ref: 'Sales!$A$2:$A$4' },
val: { ref: 'Sales!$B$2:$B$4' },
}),
],
});
const space = makeChartSpace({
plotArea: { chart },
title: 'Revenue by region',
legend: { position: 'r' },
});
addChartAt(ws, 'D2', { space }, { widthPx: 480, heightPx: 320 });
// Image at a cell — PNG / JPEG / GIF / BMP / WebP / TIFF / SVG / EMF / WMF.
// Format and dimensions are auto-detected from the bytes.
const img = loadImage(await readFile('logo.png'));
addImageAt(ws, 'B2', img, { widthPx: 200, heightPx: 80 });
```
Available chart kinds:
- Legacy `c:` family (`@office-kit/xlsx/chart`): `makeBarChart`,
`makeLineChart`, `makeAreaChart`, `makePieChart`,
`makeDoughnutChart`, `makeScatterChart`, `makeRadarChart`,
`makeBubbleChart`, `makeStockChart`, `makeSurfaceChart`,
`makeOfPieChart`. Wrap them in a `PlotArea` and pass to
`makeChartSpace`.
- Modern `cx:` chartex family (same subpath): `makeSunburstChart`,
`makeTreemapChart`, `makeWaterfallChart`, `makeHistogramChart`,
`makeParetoChart`, `makeFunnelChart`, `makeBoxWhiskerChart`,
`makeRegionMapChart`. These return a `CxChartSpace` directly.
For absolute positioning instead of a one-cell anchor, use
`makeAbsoluteAnchor` or `makeTwoCellAnchor` from `@office-kit/xlsx/drawing`.
## Multiple sheets, defined names, chartsheets
```ts
import {
createWorkbook, addWorksheet, addChartsheet, addDefinedName,
} from '@office-kit/xlsx/workbook';
const wb = createWorkbook();
addWorksheet(wb, 'Q1');
addWorksheet(wb, 'Q2');
const cs = addChartsheet(wb, 'Dashboard'); // chartsheet has no cell grid
addDefinedName(wb, { name: 'Totals', value: 'Q1!$A$1:$A$10,Q2!$A$1:$A$10' });
```
## Errors
Every error thrown by @office-kit/xlsx is a subclass of `OpenXmlError`
(`@office-kit/xlsx/utils`). Catch the specific subclass that matters:
| Error | When |
|------------------------------------|-----------------------------------------------------------------------------------------------------|
| `OpenXmlIoError` | Source / sink failure (bad bytes, broken zip, partial read, write error) |
| `OpenXmlSchemaError` | Input does not conform to the ECMA-376 OOXML schema |
| `OpenXmlDecompressionBombError` | Archive exceeded `decompressionLimits` (per-entry size, total size, or compression ratio) |
| `OpenXmlInvalidWorkbookError` | Workbook structurally invalid (missing parts, broken relationships) |
| `OpenXmlNotImplementedError` | Feature is not yet supported (e.g. ZIP64 write, encrypted decryption) |
`decompressionLimits` is **on by default** in both `loadWorkbook` and
`loadWorkbookStream`. Keep it on when reading untrusted input.
Encrypted xlsx files (CFB Compound Documents) are detected and rejected
with a clear error pointing at
[`msoffcrypto-tool`](https://github.com/nolze/msoffcrypto-tool); decrypt
externally first, then load the resulting plain xlsx.
## Common pitfalls
- **No root barrel.** `import { ... } from '@office-kit/xlsx'` doesn't work. Use a
subpath import like `@office-kit/xlsx/io`, `@office-kit/xlsx/worksheet`, etc.
- **1-indexed coordinates.** `setCell(ws, 1, 1, ...)` writes `A1`, not
`B2`.
- **`wb.sheets` is a discriminated union.** Narrow on
`kind === 'worksheet'` before reading `.sheet`; chartsheets are also
in the array but have a different shape.
- **Streaming-write requires `close` + `finalize`.** Skipping either
produces a truncated zip.
- **Streaming-write locks `` after the first row.** Configure
column widths up front.
- **ZIP64 write is not supported.** Workbooks with > 65 535 entries cannot
be written; you'll get an `OpenXmlNotImplementedError`. Read of ZIP64
archives works.
- **`.xls` / `.xlsb` / `.ods` / `.csv` are out of scope.** @office-kit/xlsx
reads and writes `.xlsx` (and `.xlsm` macro-enabled workbooks).
For older formats, reach for SheetJS.
- **Color values are AARRGGBB hex strings.** `'FF3366CC'` is opaque blue.
Forgetting the alpha prefix gives the wrong color.
- **Don't swallow `OpenXmlError`.** Internal-logic errors propagate as
themselves — they're bugs, not validation failures.
## Cheatsheet
| Task | Functions |
|------|-----------|
| Read xlsx file (Node) → Workbook | `loadWorkbook` + `fromFile` |
| Read xlsx Buffer (Node) → Workbook | `loadWorkbook` + `fromBuffer` |
| Read xlsx from `fetch` (browser) → Workbook | `loadWorkbook` + `fromResponse` |
| Read xlsx from `` | `loadWorkbook` + `fromBlob` |
| Iterate every cell of a worksheet | `iterRows` (or `iterValues`) |
| Find populated extents | `getMaxRow` + `getMaxCol` |
| Stream-read huge sheet | `loadWorkbookStream` + `openWorksheet` + `iterRows` |
| Stream-read rows N..M | `loadWorkbookStream` + `iterRows({ minRow, maxRow })` |
| Build a workbook → `Uint8Array` | `createWorkbook` + `addWorksheet` + `setCell` + `workbookToBytes` |
| Build a workbook → file (Node) | `createWorkbook` + `addWorksheet` + `setCell` + `saveWorkbook` + `toFile` |
| Build a workbook → `Buffer` (Node) | `workbookToBuffer` |
| Build a workbook → `Blob` (browser) | `toBlob` |
| Edit one cell of an existing file | `loadWorkbook` + `setCell` + `saveWorkbook` |
| Append rows | `appendRow` / `appendRows` |
| Stream-write millions of rows | `createWriteOnlyWorkbook` + `appendRow` + `ws.close` + `wb.finalize` |
| Bold + size + fill on header | `setBold` + `setFontSize` + `setCellBackgroundColor` |
| Currency / percent | `setCellAsCurrency` / `setCellAsPercent` |
| Date number format | `setCellNumberFormat` + `FORMAT_DATE_DATETIME` |
| Formula with cached value | `setCell` + `setFormula` |
| Hyperlink | `setHyperlink` |
| Merge + freeze header | `mergeCells` + `makeFreezePane` + `makeSheetView` |
| Defined name across sheets | `addDefinedName` |
| Excel Table | `addExcelTable` |
| AutoFilter only | `addAutoFilter` |
| Dropdown validation | `makeDataValidation` + `addDataValidation` |
| 3-color scale heat-map | `makeCfRule` + `makeConditionalFormatting` + `addConditionalFormatting` |
| Image at cell | `loadImage` + `addImageAt` |
| Clustered column chart | `makeBarChart` + `makeBarSeries` + `makeChartSpace` + `addChartAt` |
| A1 ↔ row/col | `coordinateFromString` / `tupleToCoordinate` (`@office-kit/xlsx/utils`) |
| Convert Date ↔ Excel serial | `dateToExcel` / `excelToDate` (`@office-kit/xlsx/utils`) |
## Getting started
- [Install](./docs/install.md): Add @office-kit/xlsx to a Node, Bun, or browser project.
- [Getting started](./docs/getting-started.md): Read, edit, and write your first xlsx workbook.
- [Recipes](./docs/recipes.md): Working code for the most common tasks — open / build / style / chart / validate / stream / export.
- [Cheatsheet](./docs/cheatsheet.md): One-page lookup of task → exact functions to import + call. The shortest path from "I want to do X" to working code.
## Guides
- [Streaming](./docs/streaming.md): Read and write multi-million row workbooks in fixed memory.
## Reference
- [API overview](./docs/api.md): Top-level exports, subpath entries, and bundle budgets.
## Source
- [GitHub repository](https://github.com/office-kit/xlsx)
- [npm package](https://www.npmjs.com/package/@office-kit/xlsx)