Guide
Build a Firebase kill switch: disable billing automatically
October 21, 2026 · 7 min read
Step by step: a Cloud Function on your budget's Pub/Sub topic that disconnects billing past your limit, with the code, the IAM role it needs and where it falls short.
What you're building
A Cloud Function subscribed to your budget's Pub/Sub topic. Every time Google publishes the budget status, the function checks the spend and, past your limit, removes the billing account from the project. Google documents this exact pattern in Disable billing usage with notifications, and Firebase points to it from its avoid surprise bills page.
Read the consequences first. Google's own warning: "This tutorial removes Cloud Billing from your project, shutting down all resources." and "Resources might be irretrievably deleted." See what happens when billing stops.
1. Connect the budget to Pub/Sub
In the Cloud console, open Billing → Budgets & alerts, edit your budget and, under notifications, connect a Pub/Sub topic (for example billing-alerts). Google then publishes the budget status to it "multiple times per day", even when there's no usage.
Each message is base64 JSON with fields like these:
{
"budgetDisplayName": "my-project budget",
"costAmount": 142.37,
"costIntervalStart": "2026-10-01T07:00:00Z",
"budgetAmount": 100,
"budgetAmountType": "SPECIFIED_AMOUNT",
"alertThresholdExceeded": 1.0,
"currencyCode": "USD"
}2. Give the function's service account billing rights
The function runs as a service account that must be able to change the project's billing. Google's tutorial grants it roles/billing.admin (Billing Account Administrator) on the billing account. That is a powerful role: use a dedicated service account for this function only.
3. The function
import { onMessagePublished } from "firebase-functions/v2/pubsub";
import { GoogleAuth } from "google-auth-library";
const PROJECT_ID = process.env.GCLOUD_PROJECT!;
export const killSwitch = onMessagePublished(
{ topic: "billing-alerts", serviceAccount: "billing-kill@YOUR_PROJECT.iam.gserviceaccount.com" },
async (event) => {
const data = event.data.message.json as { costAmount: number; budgetAmount: number };
if (data.costAmount < data.budgetAmount) return; // still under budget
const auth = new GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-billing"] });
const client = await auth.getClient();
const url = `https://cloudbilling.googleapis.com/v1/projects/${PROJECT_ID}/billingInfo`;
const { data: info } = await client.request<{ billingEnabled?: boolean }>({ url });
if (!info.billingEnabled) return; // already disabled
// An empty billing account name detaches billing from the project.
await client.request({ url, method: "PUT", data: { billingAccountName: "" } });
console.log(`Billing disabled: ${data.costAmount} of ${data.budgetAmount}`);
},
);Test it on a throwaway project with a tiny budget before you trust it with a real one.
Where the DIY version falls short
- It's all or nothing. There's no step between "keep spending" and "switch everything off", so most people set the limit high and hope.
- It inherits the reporting delay. Google itself says following these steps "doesn't guarantee that you won't spend more than your budget".
- Nobody watches the watcher. If the topic, the budget or the permission breaks, the function silently never runs.
- One project at a time. Every project needs its own budget, topic, function and IAM setup.
FireShield is this kill switch as a service: budgets synced for you, a Soft Kill webhook before the Hard Kill, alerts by email, Slack and Telegram, an audit log of every action, and usage spike detection on Pro and Enterprise.
