Cloudflare is launching Billing Budget Alerts: https://developers.cloudflare.com/billing/manage/budget-alerts/
Cloudflare Worker that checks account-wide usage every 10 minutes, alerts at 50%, 75%, and 90%, features a 15% velocity breaker to catch rapid spikes, uses KV for the breaker flag, and stores run history in D1 for the root dashboard.
- Workers daily requests:
100,000 - D1 monthly rows read:
5,000,000 - D1 monthly rows written:
100,000 - R2 monthly Class A operations:
1,000,000 - R2 monthly Class B operations:
10,000,000
src/index.js- scheduled watcher, GraphQL queries, alerts, breaker state, and D1 run history.- Root URL - renders the latest usage run from D1, including usage percentages, breaker state, recent history, and failures.
schema.sql- D1 schema for the usage history table.src/usage-breaker.js- reusable middleware-style breaker check for your other Workers.wrangler.toml- Cron trigger and KV binding.
If you want the smallest possible copy/paste version, use this directly in another Worker:
async function enforceUsageBreaker(env) {
const limitExceeded = await env.USAGE_STATE.get("LIMIT_EXCEEDED");
if (limitExceeded === "true") return new Response("Daily Limit Reached", { status: 503 });
return null;
}
export default {
async fetch(request, env, ctx) {
const breaker = await enforceUsageBreaker(env);
if (breaker) return breaker;
return new Response("OK");
},
};If you prefer a reusable helper file, use src/usage-breaker.js. If you want the same logic in a standalone pasteable module, use src/usage-breaker-inline.js.
- Create a KV namespace named
USAGE_STATE. - Bind that same namespace in this watcher and every Worker you want protected.
- Create a D1 database for the watcher history and bind it as
DB. - Create a Cloudflare API token with
Account > Analytics > Read. - Choose an email provider (
resend,mailchannels,postmark,zeptomail) and setEMAIL_PROVIDER. - Get your chosen provider's API key and set it as the secret
EMAIL_API_KEY(except for MailChannels, which doesn't need one). - If using Push alerts, choose a provider (
telegramorntfy) and setPUSH_PROVIDER, then setTELEGRAM_BOT_TOKENorNTFY_AUTH_TOKENas a secret. - Set
ALERT_TO_EMAIL,EMAIL_FROM_ADDRESS,TELEGRAM_CHAT_ID, andNTFY_TOPIC_URL(as needed). - If Cloudflare Access protects the public watcher URL, set
CF_ACCESS_AUDIENCEandCF_ACCESS_JWK_URLas vars. - Toggle
NOTIFY_VIA_EMAILandNOTIFY_VIA_PUSHinsidewrangler.tomldepending on your preferred alert channels. - Deploy the worker and confirm the cron trigger is enabled.
- Run
wrangler loginonce on your machine if you have not already. - Create the
USAGE_STATEKV namespace and copy the namespace ID intowrangler.toml. - Create or bind the D1 database and copy the database ID into
wrangler.toml. - Add the required secrets and vars:
wrangler secret put CF_API_TOKENwrangler secret put CF_ACCOUNT_IDwrangler secret put EMAIL_API_KEY(if using Resend, Postmark, or ZeptoMail)wrangler secret put TELEGRAM_BOT_TOKEN(if using Telegram)wrangler secret put NTFY_AUTH_TOKEN(if using Ntfy with authentication)- Set
EMAIL_PROVIDER,ALERT_TO_EMAIL,EMAIL_FROM_ADDRESS,PUSH_PROVIDER,TELEGRAM_CHAT_ID,NOTIFY_VIA_EMAIL, andNOTIFY_VIA_PUSHinwrangler.tomlor via dashboard vars.
- Deploy with
wrangler deploy. - Confirm the scheduled trigger appears in the Cloudflare dashboard and that the Worker logs show a successful cron run.
If you created a brand-new D1 database, apply the schema once before the first deploy:
npx wrangler d1 execute usage-watcher --remote --file ./schema.sqlIf you are testing locally, swap --remote for --local.
- Workers reset daily at
00:00 UTC. - D1 and R2 reset on the 1st of the month.
- Velocity Tracker: If a monitored metric spikes by exactly or more than 15% of its entire absolute limit within a single 10-minute snapshot interval, it gets treated as an active breach (Surge), regardless of whether it hit the 50% absolute threshold yet. This protects free tiers from sudden attacks instantly throwing the circuit breaker.
- When a metric returns to zero, the watcher clears that metric’s alert-cooling state and removes it from the active breach list.
LIMIT_EXCEEDEDstaystruewhile any metric remains in breach state and flips back tofalseonce all active breach flags are cleared.- The root page keeps the latest 10 cron snapshots in D1, and runs older than 90 days are pruned automatically.
If you enable Cloudflare Access on the watcher URL, the worker validates the Cf-Access-Jwt-Assertion header before rendering the dashboard.
CF_ACCESS_AUDIENCEshould match the Access application audience.CF_ACCESS_JWK_URLshould point at the Access JWKs endpoint.TIME_ZONEcontrols the local timezone shown next to the UTC "Last checked" timestamp. Default:America/Chicago./healthremains available for simple health checks./debug/accessreturns a safe JSON diagnosis of why Access validation passed or failed./debug/test-alertsends a manual alert email through the configured provider as a real threshold breach.- The dashboard also exposes
window.sendUsageWatcherTestAlert()in the browser console.
The following variables and secrets must be configured before deployment. Update these inside wrangler.toml or via the Cloudflare Dashboard (Settings > Variables and Secrets).
Infrastructure Bindings (in wrangler.toml)
kv_namespaces.id: Replace#REPLACE WITH YOURSwith your KV Namespace ID.d1_databases.database_id: Replace#REPLACE WITH YOURSwith your D1 Database ID.
Secrets (add via wrangler secret put <NAME>)
CF_API_TOKEN: Cloudflare API token withAccount > Analytics > Readpermissions.CF_ACCOUNT_ID: Your Cloudflare Account ID.EMAIL_API_KEY: API Key for your chosen email provider (not needed for MailChannels).TELEGRAM_BOT_TOKEN: (Optional) Your Telegram bot token ifPUSH_PROVIDER="telegram".NTFY_AUTH_TOKEN: (Optional) Authentication token if yourntfytopic is protected.
Environment Variables (in wrangler.toml [vars] block)
ENABLE_PUBLIC_DASHBOARD:"true"or"false". If false, visiting the worker route will return a 404 (but background alerts will continue running).ENABLE_CLOUDFLARE_ACCESS:"true"or"false". WARNING: If"false", the public dashboard is entirely open to the internet.NOTIFY_VIA_EMAIL:"true"or"false"EMAIL_PROVIDER:"resend","mailchannels","postmark", or"zeptomail"ALERT_TO_EMAIL: The recipient email address for alerts.EMAIL_FROM_ADDRESS: The sender email address.EMAIL_FROM_NAME: The sender name (e.g., "Usage Watcher").NOTIFY_VIA_PUSH:"true"or"false"PUSH_PROVIDER:"telegram"or"ntfy"TELEGRAM_CHAT_ID: Your Telegram Chat ID.NTFY_TOPIC_URL: Your full Ntfy topic URL (e.g.,https://ntfy.sh/my_topic).CF_ACCESS_JWK_URL: Update<your-team-name>to your Zero Trust team name if using Cloudflare Access.CF_ACCESS_AUDIENCE: The Audience Tag for your Access application.TIME_ZONE: The local timezone for snapshot timestamps (e.g.,"America/Chicago").
- The watcher uses Cloudflare's global GraphQL endpoint for analytics queries:
https://api.cloudflare.com/client/v4/graphql
Email (EMAIL_PROVIDER)
resend: (Default) SetEMAIL_API_KEYto your Resend API Key.mailchannels: Free email routing from Cloudflare Workers. No API key required.postmark: SetEMAIL_API_KEYto your Postmark server token.zeptomail: SetEMAIL_API_KEYto your ZeptoMail token.
Push (PUSH_PROVIDER)
telegram: (Default) RequiresTELEGRAM_BOT_TOKENandTELEGRAM_CHAT_ID.ntfy: RequiresNTFY_TOPIC_URL(e.g.https://ntfy.sh/my_topic). Optionally setNTFY_AUTH_TOKENif your topic is protected.
Security Notice: By default, if ENABLE_CLOUDFLARE_ACCESS is set to "false", the public status dashboard built-in to this application will be exposed to the entire internet with no authentication. The code has no built-in password or token security mechanism. You are strongly advised to leave ENABLE_CLOUDFLARE_ACCESS="true" and configure a Cloudflare Access Zero Trust policy to securely protect the route.
Cloudflare, Telegram, ZeptoMail, Postmark, Resend, MailChannels, Ntfy, and any other brand names, product names, or service marks mentioned in this document are the trademarks, registered trademarks, or service marks of their respective owners. This open-source project is provided for educational and utility purposes only, and is not affiliated with, endorsed by, or sponsored by Cloudflare, Inc. or any other brand mentioned herein.
AI Generation Notice: Portions of this code and its accompanying documentation may have been generated or assisted by Artificial Intelligence (AI). While efforts have been made to ensure accuracy, AI-generated code can contain bugs, security vulnerabilities, or unintended behaviors. You are strongly cautioned to independently review, test, and audit all code and configurations to ensure they meet your specific security, compliance, and production requirements before deployment.