bilig

Google Sheets API alternative for local Node workbook execution

If a real spreadsheet with sharing, permissions, comments, and a URL is the product, use Google Sheets API.

If the spreadsheet logic belongs inside a Node service, queue worker, CLI, or agent tool, use @bilig/headless. Keep the workbook local, write inputs, read calculated cells, and persist the WorkPaper document as JSON.

That is the boundary. bilig is not trying to replace Google Sheets. It is for code that needs sheet-shaped business logic without turning a hosted spreadsheet into the system of record.

Quick decision

You need Start with
People editing the same hosted spreadsheet Google Sheets
OAuth, spreadsheet IDs, A1 ranges, and Google Workspace permissions Google Sheets API
A Node service that owns workbook state and formula execution @bilig/headless
An agent tool that edits a cell and returns checked readback @bilig/headless
An XLSX file for a person to open later SheetJS, ExcelJS, or Excel automation

Google describes the Sheets API as a REST interface for reading and modifying spreadsheet data. Its values guide is built around the spreadsheets.values resource, spreadsheet IDs, and ranges. That is the right shape when the spreadsheet already lives in Google Sheets.

A WorkPaper is smaller. Your code owns the workbook object. It does not need OAuth or a network round trip to recalculate a dependent cell.

TypeScript smoke test

Run this from an empty Node project:

mkdir bilig-google-sheets-api-boundary
cd bilig-google-sheets-api-boundary
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"],
    ["Units", 300],
    ["Price", 19],
    ["Discount", 0.1],
  ],
  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 }, 420);

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,
      verified: before === 5130 && after === 7182 && afterRestore === 7182,
    },
    null,
    2,
  ),
);

workbook.dispose();
restored.dispose();
EOF
npx tsx eval.ts

Expected output:

{
  "before": 5130,
  "after": 7182,
  "afterRestore": 7182,
  "verified": true
}

That proves the local backend loop: build workbook state, change one input, read the recalculated value, save JSON, restore the workbook, and read the same value again.

Use Google Sheets API when

Do not move that workflow to bilig just to avoid an API call.

Use a local WorkPaper when

The useful unit is a state transition your code can test: input changed, formulas recalculated, output read, document saved.

If this boundary saves you a Google Sheets automation spike, star the repository so the next backend developer 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.

Sources