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

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.