JavaScript SDK
The witan npm package installs the Witan CLI and a Node.js SDK. The SDK opens a witan xlsx rpc subprocess, keeps a workbook session alive, and exposes the spreadsheet API through promise-based Workbook methods.
Use it when a Node.js or TypeScript application needs to read, write, search, recalculate, render, or lint Excel workbooks programmatically.
Install
npm install witan
The package installs the SDK plus the platform-specific Witan binary. Node.js 22 or later is required.
Open a workbook
import { Workbook } from 'witan';
await using wb = await Workbook.open('report.xlsx');
const sheets = await wb.listSheets();
const tsv = await wb.readRangeTsv('Summary!A1:F20');
console.log(sheets.map(sheet => sheet.sheet));
console.log(tsv);
Workbook implements AsyncDisposable, so await using closes the subprocess automatically.
If your runtime does not use explicit resource management, close the workbook in finally:
import { Workbook } from 'witan';
const wb = await Workbook.open('report.xlsx');
try {
const tsv = await wb.readRangeTsv('Summary!A1:F20');
console.log(tsv);
} finally {
await wb.close();
}
Create and save
import { Workbook } from 'witan';
await using wb = await Workbook.open('model.xlsx', { create: true });
await wb.addSheet('Inputs');
const result = await wb.setCells([
{ address: 'Inputs!A1', value: 'Revenue' },
{ address: 'Inputs!B1', value: 1250000 },
{ address: 'Inputs!C1', value: null, formula: '=B1*1.1' },
]);
if (result.errors.length > 0) {
throw new Error(JSON.stringify(result.errors));
}
await wb.save();
Writes update the live workbook session and recalculate dependent formulas. Call save() when you want to write the workbook bytes back to disk.
Common workflows
Read and search
import { Workbook } from 'witan';
await using wb = await Workbook.open('report.xlsx');
const cell = await wb.readCell('Summary!B5');
const rows = await wb.readRangeTsv('Summary!A1:F20');
const matches = await wb.findCells(/revenue|ebitda/i, {
in: 'Summary!A:Z',
context: 2,
});
Use readRangeTsv, readRowTsv, and readColumnTsv when passing workbook data to a language model. TSV is usually smaller and easier to scan than nested JSON.
Edit and verify
await using wb = await Workbook.open('report.xlsx');
const before = await wb.previewStyles('Summary!A1:F20');
const result = await wb.setCells([
{ address: 'Summary!B5', value: null, formula: '=SUM(Revenue!B2:B13)' },
]);
const lint = await wb.lint({ rangeAddresses: ['Summary!A1:F20'] });
const after = await wb.previewStyles('Summary!A1:F20');
if (result.errors.length > 0 || lint.total > 0) {
throw new Error(JSON.stringify({ formulaErrors: result.errors, lint }));
}
await wb.save();
previewStyles() returns a data:image/...;base64,... URL for the rendered range. For command-line image files and pixel diffs, use witan xlsx render.
Trace and run scenarios
await using wb = await Workbook.open('model.xlsx');
const inputs = await wb.traceToInputs('Summary!F25');
const sweep = await wb.sweepInputs(
[{ address: 'Inputs!B5', values: [0.02, 0.04, 0.06] }],
['Summary!F25'],
{ mode: 'cartesian', includeStats: true }
);
console.log(sweep.tsv);
Author tables and charts
await using wb = await Workbook.open('dashboard.xlsx');
await wb.addListObject('Sheet1', {
name: 'SalesTable',
ref: 'A1:C4',
showTotalsRow: true,
columns: [
{ name: 'Region', totalsRowLabel: 'Total' },
{ name: 'Sales', totalsRowFunction: 'sum' },
{ name: 'DoubleSales', calculatedColumnFormula: '=B2*2' },
],
rows: [
[{ value: 'North' }, { value: 10 }, {}],
[{ value: 'South' }, { value: 20 }, {}],
],
});
await wb.addChart('Sheet1', {
name: 'Revenue',
position: { from: { cell: 'F2' }, to: { cell: 'N18' } },
groups: [
{
type: 'column',
series: [
{
name: { ref: 'Sheet1!B1' },
categories: 'Sheet1!A2:A4',
values: 'Sheet1!B2:B4',
},
],
},
],
title: { text: 'Revenue' },
legend: { position: 'right' },
});
await wb.previewStyles('Sheet1!F2:N18');
await wb.save();
Method map
The JavaScript SDK follows the xlsx exec API with camelCase names:
| Area | Methods |
|---|---|
| Workbook and sheets | listSheets, addSheet, deleteSheet, renameSheet, getSheetProperties, setSheetProperties |
| Reading | readCell, readRange, readRow, readColumn, readRangeTsv, readRowTsv, readColumnTsv |
| Search | findCells, findRows, findAndReplace, describeSheet, tableLookup |
| Writing | setCells, copyRange, scaleRange, insertRowAfter, deleteRows, insertColumnAfter, deleteColumns |
| Formulas | evaluateFormula, evaluateFormulas, getCellPrecedents, getCellDependents, traceToInputs, traceToOutputs, sweepInputs |
| Verification | lint, previewStyles |
| Tables and charts | getListObject, addListObject, setListObject, getDataTable, addDataTable, listCharts, getChart, addChart, setChart |
Options
Pass options to Workbook.open:
const wb = await Workbook.open('report.xlsx', {
locale: 'en-US',
stateless: true,
apiKey: 'sk_...',
apiUrl: 'https://api.witanlabs.com',
requestTimeoutMs: 60_000,
});
Most configuration can also come from environment variables. See Configuration.
CLI from npm
The npm package also exposes the witan command:
npx witan xlsx calc report.xlsx
npx witan xlsx render report.xlsx -r "Summary!A1:F20"
npx witan xlsx lint report.xlsx
Errors
import {
Workbook,
WitanError,
WitanProcessError,
WitanRPCError,
WitanTimeoutError,
} from 'witan';
try {
await using wb = await Workbook.open('report.xlsx');
await wb.readRange('InvalidRange!!!');
} catch (err) {
if (err instanceof WitanTimeoutError) {
console.error('Request timed out:', err.message);
} else if (err instanceof WitanRPCError) {
console.error('RPC error:', err.code, err.message);
} else if (err instanceof WitanProcessError) {
console.error('Process error:', err.message);
} else if (err instanceof WitanError) {
console.error('Witan error:', err.message);
}
}
Next steps
- Python SDK — use the same workbook API from Python.
- CLI — run one-off workbook scripts with
witan xlsx exec. - Concepts — understand the shared workbook model underneath every SDK.