Google Sheets API alternative for local Node workbook execution

May 16, 2026 ยท View on GitHub

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 needStart with
People editing the same hosted spreadsheetGoogle Sheets
OAuth, spreadsheet IDs, A1 ranges, and Google Workspace permissionsGoogle 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 laterSheetJS, 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

  • users already collaborate in a Google Sheet
  • the spreadsheet URL is the durable artifact
  • Google permissions are part of the product
  • your app reads or writes ranges in an existing Google Sheet
  • reviewers expect to inspect the result in Google Workspace

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

Use a local WorkPaper when

  • a route handler, job, CLI, or coding agent owns the flow
  • the workbook is service state, not a shared document
  • tests should run without network credentials
  • the app needs checked before/after values after one input changes
  • formula-backed state should sit beside the rest of the service data

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