Firebase custom claims as feature flags
How to use Firebase Auth custom claims as server‑driven feature flags, with a concrete Admin SDK setup, client consumption, and propagation considerations.
13 Sept 2025, 06:52 UTC

Many teams need to release a new UI capability to a subset of users without waiting for a full app update. Firebase Auth custom claims can serve as server‑driven feature flags: a claim is set once on a user’s ID token and then read directly by the client, eliminating the need for a separate config store or per‑request database look‑ups.
Problem: conditional rollout without a new release
Imagine you are adding a "beta export" button to your web app. You want a controlled group of users to see it today, and everyone else next month. Traditionally you would push a client‑side config file, add a remote config parameter, or create a database flag that each request checks. Each approach adds latency, cost, or requires a code change and redeploy.
Thesis: custom claims as lightweight, token‑bound flags
Custom claims travel inside the signed ID token that client SDKs attach to every request. Because the token is already present for authentication, checking a claim costs no extra round‑trip and works offline. This makes them ideal for feature-flag use‑cases where the decision lives on the server and propagates automatically after the next sign‑in or token refresh.
1️⃣ Setting the claim from a backend
Use the Firebase Admin SDK (Node, Python, Go, etc.) in a trusted environment (Cloud Function, CI job, or local script with service‑account credentials). The following snippet sets a claim named betaExport to true for a given UID:
// Node.js Admin SDK – run with a service account that has the firebaseauth.admin scope
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
await admin.auth().setCustomUserClaims(uid, {
betaExport: true
});
Where to run it: any environment where you can import the Admin SDK and authenticate with a service account having the firebaseauth.admin permission. Typical places: a Cloud Function triggered by an admin UI, a CI pipeline step, or the Firebase CLI after firebase login:ci.
Required permissions: the service account needs the roles/firebaser.admin role (or finer‑grained IAM) that includes firebaseauth.setCustomUserClaims.
Expected check: after the command runs, sign in as the target user (or force a token refresh) and decode the ID token (e.g., jwt.decode(token, null, ['RS256'])) to verify that betaExport appears with the intended boolean value.
Risk: custom claims only take effect after the user’s ID token is refreshed, which can take up to an hour. If you need immediate effect, pair the claim with a server‑side denylist or short‑lived token custom claims refreshed via a Cloud Function.
2️⃣ Reading the claim in the client
Client SDKs (Web, iOS, Android) expose the ID token through the auth instance. In JavaScript, for example:
const token = await firebase.auth().currentUser.getIdTokenResult();
const isBeta = token.claims.betaExport; // true or undefined
You can then use isBeta to conditionally render UI elements, enable APIs, or adjust analytics events.
3️⃣ Concrete worked example – beta export button
Assume you have a button with id exportBtn. After the user signs in, run:
firebase.auth().onAuthStateChanged(async user => {
if (!user) return;
const idTokenResult = await user.getIdTokenResult();
const canExport = idTokenResult.claims?.betaExport;
exportBtn.style.display = canExport ? 'block' : 'none';
});
When the Admin SDK sets betaExport: true for a user, the next time that user signs in (or their token refreshes) the button appears. Removing the claim or setting it to false hides it again.
Trade‑off: propagation delay and claim limits
- Propagation delay: Claims are not instantaneous. A change made via the Admin SDK propagates only when the user’s ID token is refreshed – typically on sign‑in, sign‑out, or after
firebase.auth().updateCurrentUser(). In the worst case, it can take up to an hour. - Claim size limit: The total custom claim payload is capped at roughly 1000 bytes. Keep flags small (boolean or short string) to stay well within this limit.
- Not a replacement for Firestore rules: Custom claims are great for client‑level feature toggles, but Firestore Security Rules should still enforce data‑level permissions. Use claims for UI gating; keep backend authorization in rules.
Actionable closing
- Pick a feature flag name (e.g.,
betaExport) and decide the data type you need (boolean, string, numeric). - Write a small script that calls
admin.auth().setCustomUserClaims(uid, { flagName: value })and run it once for a test user. - Sign in as that user with your app, fetch the ID token result, and confirm the claim appears.
- Toggle the claim value and observe the UI update after the next sign‑in or token refresh.
- If you need faster rollout, combine custom claims with Firebase Remote Config or a server‑side denylish checked on each request.
Used thoughtfully, custom claims give you a code‑free way to gate features per‑user, reduce deployment cycles, and keep the authorization logic co‑located with the authentication state you already have.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.