Firebase Quota Exceeded — Free Tier Hard Limits

Firestore writes / Functions calls / Hosting bandwidth — each has a hard line on free tier.

Your Firebase project is fully broken: Firestore reads fail, Functions all 503, Hosting returns quota errors — and you didn’t touch any code. The console banner says “Quota exceeded for quota metric …” This is either Spark (free) hitting a hard cap, or Blaze hitting a per-day quota. The real problem isn’t that the quota filled up — it’s that you never set an alert, so you only found out when it overflowed.

Mental model: Firebase isn’t one quota — it’s dozens of metered metrics (Firestore reads / writes / deletes / storage GB / Functions invocations / GB-seconds / Hosting bandwidth / Auth verifications…). Any single one hitting its cap = 503.

Common causes

Ordered by hit rate, highest first.

1. Spark plan hit a hard cap

Spark is a hard ceiling (cannot overspend). Common red lines:

Firestore reads:       50K / day
Firestore writes:      20K / day
Firestore deletes:     20K / day
Firestore storage:     1 GB
Functions invocations: 125K / month
Hosting bandwidth:     10 GB / month
Auth verifications:    unlimited (but phone auth = 100 / day)

How to spot it: Firebase Console → Usage and billing → which is at 100%.

2. Function loop burning invocations

Classic anti-pattern:

// Write to Firestore triggers onWrite → which writes Firestore → ...
exports.onUserUpdate = onDocumentWritten('users/{uid}', async (event) => {
  await db.doc(`users/${event.params.uid}`).update({...}); // ❌ triggers self
});

Can burn through a month’s free quota in 24 hours.

How to spot it: Console → Functions → single function with absurdly high invocations (millions).

3. Client loop reading Firestore

function Comp() {
  useEffect(() => {
    onSnapshot(query, snap => {
      setItems(snap.docs);  // re-render → useEffect → subscribe again
    });
  }); // ❌ missing deps array
}

10 active users can drain free quota in an hour.

How to spot it: Console → Firestore → Usage → unusual reads/sec.

4. Large files / video bust Hosting bandwidth

100MB video on Firebase Hosting × 1000 viewers = 100GB, 10x the free quota.

How to spot it: Hosting bandwidth absurdly high relative to traffic.

5. No Firestore TTL, old data piles up

Logs / sessions / analytics stored in Firestore without cleanup — eventually fills 1GB.

How to spot it: Storage > 1GB but user-data volume doesn’t justify it.

6. Scraper / attacker hitting your data

Loose Firestore security rules → attacker finds the endpoint and queries anything → bandwidth and reads explode.

How to spot it: User count unchanged but reads spike 100x.

Shortest path to fix

Step 1: Identify which quota tripped

Firebase Console → Usage and billing
  → review Firestore / Functions / Hosting / Auth metrics
  → the red 100% one is your culprit

Read the dashboard — don’t guess.

Step 2: Emergency relief — Blaze + hard caps

If you need to restore service now, upgrade to Blaze (pay-as-you-go), but set Budget Cap and Alerts immediately:

1. GCP Console → Billing → Budgets & alerts → Create budget
2. Budget amount: $20 (the max monthly you'll tolerate)
3. Alert thresholds: 50%, 90%, 100%
4. Alert recipients: your email
5. Important: enable "Disable billing" action to prevent runaway

Note: Blaze can’t truly hard-cap spend — it’s alert + auto-disable. Already-incurred charges still bill.

Step 3: Locate the runaway

Source = client script / Function loop / attacker

Function loop:
  Console → Functions → find the one with insane invocations → Disable

Client loop:
  Roll back the latest frontend
  Or hotfix the useEffect with deps array

Attacker:
  Tighten Firestore rules immediately (match /{document=**} { allow read, write: if false; })
  Then carefully add back the rules you actually need

Step 4: Guard Functions against self-triggering

exports.onUserUpdate = onDocumentWritten('users/{uid}', async (event) => {
  // If triggered by our own _serverUpdate flag, skip to avoid loop
  const after = event.data?.after.data();
  if (after?._serverUpdate) return;

  await db.doc(`users/${event.params.uid}`).update({
    derived: ...,
    _serverUpdate: true,  // mark
  });
});

Or cleaner — split into two collections to isolate write paths.

Step 5: Fix client useEffect deps

useEffect(() => {
  const unsub = onSnapshot(query, snap => setItems(snap.docs));
  return () => unsub();
}, []);  // ✅ empty deps

Step 6: Tighten Firestore rules

Least privilege:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read, write: if request.auth.uid == uid;
    }
    match /posts/{post} {
      allow read: if true;
      allow write: if request.auth != null
                   && request.resource.data.authorId == request.auth.uid;
    }
  }
}

Deploy: firebase deploy --only firestore:rules

Step 7: Reduce unnecessary reads

// Slow: full fetch every time
const all = await getDocs(collection(db, 'posts'));

// Fast: paginate + cache
const first = await getDocs(query(
  collection(db, 'posts'),
  orderBy('createdAt', 'desc'),
  limit(20)
));

// In React, use SWR / TanStack Query to cache and dedupe fetches

Prevention

  • Set GCP Budget Alerts (50% / 90% / 100%) before launching any project — email notifications
  • Tight Firestore rules — default deny, allow only what you need
  • Functions never subscribe to a collection they also write to; if you must, use a flag to break the loop
  • Always provide a deps array to useEffect; enforce with eslint
  • Don’t host large files on Firebase Hosting — use R2 / S3 + CDN
  • Add Firestore TTL policies for logs / sessions / analytics
  • Monitor reads/sec and writes/sec; alert on anomalous spikes
  • Estimate monthly cost before launch: (daily reads × 30 × $0.06/100K) + ... so you know what’s normal

Tags: #Firebase #Debug #Troubleshooting