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 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
- 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.
Related paths
- Server-side spreadsheet automation in Node.js
- Node service WorkPaper recipe
- Persist formula-backed WorkPaper documents in Node
- Agent spreadsheet tool-call loop
- MCP spreadsheet tool server
- Where bilig is not Excel-compatible yet
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
- Google Sheets API overview: https://developers.google.com/workspace/sheets/api/guides/concepts
- Google Sheets API read and write values guide: https://developers.google.com/workspace/sheets/api/guides/values