bilig

Server-side spreadsheet automation in Node.js

Server-side spreadsheet automation is useful when the spreadsheet is logic, not the user interface. A Node service may need to price a quote, check a budget, score an import, or run a forecast using formulas that already exist in a workbook-shaped model.

Use @bilig/headless for that middle case: the service owns a workbook object, changes narrow input cells, reads calculated outputs, and stores the WorkPaper document as JSON for the next request or job.

The service boundary

Keep the automation boundary small:

  1. Load or create a WorkPaper.
  2. Apply one business input.
  3. Read the dependent result through the workbook API.
  4. Serialize the WorkPaper document.
  5. Return the value and the checks that prove the edit mattered.

That is a better backend contract than screen scraping a grid or duplicating spreadsheet formulas in application code.

TypeScript smoke test

Run this from an empty Node project:

mkdir bilig-server-automation-eval
cd bilig-server-automation-eval
npm init -y
npm pkg set type=module
npm install @bilig/headless
npm install -D tsx typescript @types/node
cat > eval.ts <<'EOF'
import {
  WorkPaper,
  createWorkPaperFromDocument,
  exportWorkPaperDocument,
  parseWorkPaperDocument,
  serializeWorkPaperDocument,
} from '@bilig/headless'

type NumericCell = {
  value: number
}

function numberValue(cell: unknown, label: string): number {
  if (typeof cell === 'object' && cell !== null && typeof (cell as NumericCell).value === 'number') {
    return (cell as NumericCell).value
  }

  throw new Error(`expected ${label} to be numeric, got ${JSON.stringify(cell)}`)
}

const workbook = WorkPaper.buildFromSheets({
  Inputs: [
    ['Metric', 'Value'],
    ['Customers', 25],
    ['ARPA', 140],
    ['Discount', 0.05],
  ],
  Summary: [
    ['Metric', 'Value'],
    ['Net revenue', '=Inputs!B2*Inputs!B3*(1-Inputs!B4)'],
  ],
})

const inputs = workbook.getSheetId('Inputs')
const summary = workbook.getSheetId('Summary')
if (inputs === undefined || summary === undefined) {
  throw new Error('missing sheet')
}

const before = numberValue(workbook.getCellValue({ sheet: summary, row: 1, col: 1 }), 'before revenue')
workbook.setCellContents({ sheet: inputs, row: 1, col: 1 }, 42)

const after = numberValue(workbook.getCellValue({ sheet: summary, row: 1, col: 1 }), 'after revenue')
const saved = serializeWorkPaperDocument(exportWorkPaperDocument(workbook, { includeConfig: true }))
const restored = createWorkPaperFromDocument(parseWorkPaperDocument(saved))
const restoredSummary = restored.getSheetId('Summary')
if (restoredSummary === undefined) {
  throw new Error('missing restored Summary sheet')
}

const afterRestore = numberValue(restored.getCellValue({ sheet: restoredSummary, row: 1, col: 1 }), 'restored revenue')

console.log(
  JSON.stringify(
    {
      before,
      after,
      afterRestore,
      persistedBytes: saved.length,
      verified: before === 3325 && after === 5586 && afterRestore === 5586,
    },
    null,
    2,
  ),
)
EOF
npx tsx eval.ts

Expected output:

{
  "before": 3325,
  "after": 5586,
  "afterRestore": 5586,
  "persistedBytes": 1024,
  "verified": true
}

The byte count can change between package versions. The important check is that verified is true and the restored workbook returns the same computed value.

What to automate

Good server-side fits:

Workflow Service input WorkPaper output
Quote approval discount, quantity, term total price and approval status
Budget variance department actuals variance amount and review flag
Subscription forecast churn, expansion, new seats ending MRR and ARR
Import validation normalized records totals, rejected rows, and formulas
Agent tool call one changed assumption before/after values and persistence proof

This is not an XLSX styling library and it is not full Excel automation. Use ExcelJS or SheetJS when the main artifact is an Excel file. Use HyperFormula when broad Excel-compatible formula coverage is the primary requirement. Use @bilig/headless when a Node process needs a small workbook model it can edit, verify, and save.

Next paths

If this matches a backend workflow you maintain, star the repository so the next developer searching for spreadsheet automation in Node can find it: https://github.com/proompteng/bilig/stargazers.

If it almost matches but a gap blocks adoption, use the adoption blocker form: https://github.com/proompteng/bilig/discussions/new?category=general.