App Guides. how to use the apps of traveller.org
Guides / Expense Tracker / How It Is Built Expense Tracker

How It Is Built

What runs where, why the Google key is kept on the server, and how to deploy, roll back, and change settings.

This is the technical page. The three guides before it describe using the app; this one describes the machinery underneath, for whoever has to change or repair it.

The short version: a small web page, and one program on Cloudflare’s network that serves that page and does the one job the page cannot safely do itself — talking to Google.

What runs where

Your browser                    lineentry.traveller.org
  the form  ─── POST /api/sheets ──▶  Cloudflare Worker "lineentry"
                                        │  holds the Google key
                                        │  signs a request to Google

                                      Google Sheets API


                                      the Budget spreadsheet

Everything is one deployable. The Worker serves the built web page and answers the one API address, /api/sheets. There is no separate server, no database, and nothing else to keep running — the spreadsheet is the only data store.

Requests for ordinary files — the page, its stylesheet, its icons — are served straight from Cloudflare’s edge and never wake the Worker at all. Only /api/sheets, and requests for things that do not exist, reach the program.

Why the Google key lives on the server

This is the single most important thing to understand before changing anything.

The app signs in to Google as a dedicated service account, using a private key. That key grants read and write access to the entire Budget workbook — not just the expense tab, but every tab in it, including the ones holding compensation and severance figures.

Until August 2026 that key was built into the web page itself. Anything in a web page is readable by anyone who opens it, so the key was effectively published: no password, no trick required, just view the source. It has since been replaced, and the exposed one revoked.

So the rule now, and the reason the Configure window cannot change the spreadsheet:

The key exists only in the Worker’s settings on Cloudflare. It is never sent to the browser, and nothing that reaches the browser may ever contain it.

The deploy refuses to publish if it finds a key in the built files — a check that exists precisely because this went wrong once and nobody noticed for weeks.

What the API address can and cannot do

The app has no login, so anyone who knows the address /api/sheets can call it. It is deliberately built to make that not matter much. It can:

  • add one row to a tab on the allowed list
  • report the spreadsheet’s name, so the page can show what it is connected to

It cannot:

  • read any cell of any tab — there is no code path that returns spreadsheet contents
  • write to any tab outside the allowed list, so the compensation and severance tabs are unreachable even though the key itself could technically touch them
  • accept anything but the exact set of categories, currencies, months and expense types the form offers, each checked again on the server
  • be used to smuggle a formula into the sheet: a note beginning with =, +, - or @ is forced to plain text, because a live formula could otherwise pull data out of tabs the app is not allowed to read

The worst an outsider can do is add junk rows to the expense tab. That is annoying rather than dangerous, and Google Sheets’ own version history undoes it. Adding a password is the fix if it ever stops being acceptable.

Deploying a change

Pushing to the main branch is what publishes. Nothing else does.

  1. A change is committed to github.com/brarob100/expense-tracker.
  2. A GitHub Actions run builds the page, checks the built files contain no Google key, and publishes the Worker.
  3. About a minute later the new version is live. Reload the page to see it.

Rolling back a bad change

  1. Open Cloudflare.
  2. Go to Workers & Pages → lineentry → Deployments.
  3. Pick the last version that was working and roll back to it.

This is instant and does not need a code change. The same instructions are on the app’s own page, under How to update this app.

Settings that live in Cloudflare

Four settings live on the Worker rather than in the code, under Workers & Pages → lineentry → Settings → Variables and Secrets:

SettingWhat it is
SHEETS_SPREADSHEET_IDwhich spreadsheet to write to
SHEETS_ALLOWED_TABSwhich tabs may be written to, separated by commas
GOOGLE_SA_EMAILthe Google account the app signs in as
GOOGLE_SA_PRIVATE_KEYits private key — stored as a Secret, so it can be replaced but never read back

The first three are also written into the project’s configuration file, so a deploy restores them. The key is not, and must never be.

Changing the spreadsheet or the tabs is covered step by step in When Something Goes Wrong, and inside the app under Configure. No code change is needed for either.

Replacing the Google key

Do this if the key is ever exposed, or as routine hygiene.

  1. In Google Cloud, open the service account and create a new JSON key. Do not delete the old one yet.
  2. In Cloudflare, edit GOOGLE_SA_PRIVATE_KEY and paste the new key’s private_key value, including its \n sequences.
  3. Wait, then check the app still shows Connected.
  4. Only then delete the old key in Google Cloud.

One trap worth knowing. The Worker remembers its Google sign-in for up to an hour, so for a while after a key change it may keep working on the old one. A successful test straight after a change proves less than it appears to. To force a genuine test, redeploy — that starts the program fresh — and then check.

Where the code is

PartWhere
The page you seesrc/ — React and TypeScript, built with Vite
The Workerworker/index.ts (routing) and worker/sheets.ts (the Google work)
Hosting configurationwrangler.toml
Publishing.github/workflows/cloudflare-deploy.yml
These guidesdocs/guides/
Full technical notesCLAUDE.md in the repository

CLAUDE.md is the deep reference — the decisions, the things that fail silently if changed, and the incident history. Read it before changing the code.

Things that will bite you

  • The column order A–H is load-bearing. The spreadsheet’s own formulas read from fixed positions. Reordering, inserting or deleting a column on the expense tab makes new entries land in the wrong places with no error at all. See The Spreadsheet Behind It.
  • The list of categories exists in two places — the form and the server’s check. Adding one to the form alone produces a choice that always fails to save.
  • Changing a setting in Cloudflare takes effect immediately, with one exception: the Google sign-in, as described above.
  • Deploys replace the Worker’s plain settings with whatever the configuration file says. That is why the three non-secret ones are written there — a deploy once wiped them and the app went down until they were restored.
  • There are no automated tests. Changes are verified by hand, which means a change that looks harmless can still break saving. After anything that touches the form or the Worker, save one real expense and confirm it lands in the sheet.

A brief history of where it has lived

It began as a Bolt.new project published by dragging a folder onto Netlify. In August 2026 it moved to a Netlify site that rebuilt from GitHub automatically, and the Google key was moved out of the web page and onto the server. In September 2026 it moved again, to a single Cloudflare Worker at lineentry.traveller.org, which is where it lives now. Netlify has been retired and the key that was held there deleted.