Most feature flag systems use static rules, but what if your app needs dynamic, real-time logic? In this guide, you'll learn how to build context-aware feature flags in JavaScript — rules that adapt to users, environments, or runtime data.
We'll use JavaScript expressions and safe evaluation to keep your flags flexible and powerful.
Step 1: Define Flags as Expressions with Context Placeholders
Instead of just true
or false
, define flags as JavaScript expressions:
const fallbackFlags = {
darkMode: "user.preferences.includes('dark')",
showBetaBanner: "env === 'staging'",
enableCart: "user.country !== 'FR' && timeOfDay !== 'night'",
};
These can adapt to different runtime variables like the logged-in user, deployment environment, or client time.
Step 2: Create a Safe Evaluator for Contextual Flags
Use a sandboxed approach to evaluate expressions with injected context:
function evaluateFlags(flags, context) {
const results = {};
for (const [key, expr] of Object.entries(flags)) {
try {
const fn = new Function(...Object.keys(context), `return (${expr})`);
results[key] = !!fn(...Object.values(context));
} catch (e) {
console.warn(`Flag "${key}" failed to evaluate:`, e);
results[key] = false;
}
}
return results;
}
You decide which context keys are safe and relevant — this could include user
, env
, timeOfDay
, region
, or anything else your app exposes.
Step 3: Build a Context Generator at Runtime
Define your context values dynamically based on real user/app data:
function getFlagContext(user) {
return {
user,
env: import.meta.env.MODE || process.env.NODE_ENV,
timeOfDay: new Date().getHours() < 6 ? 'night' : 'day',
};
}
This allows flag logic to react to time-based rules, testing environments, or geolocation.
Step 4: Cache Remote or Last Known Flags
Use localStorage to persist previously fetched flags for offline support:
async function loadFlags() {
try {
const res = await fetch('/config/flags.json');
if (!res.ok) throw new Error('Bad response');
const flags = await res.json();
localStorage.setItem('lastFlags', JSON.stringify(flags));
return flags;
} catch (e) {
const cached = localStorage.getItem('lastFlags');
return cached ? JSON.parse(cached) : fallbackFlags;
}
}
You get resiliency for free — remote failure won't break your flag layer.
Step 5: Put It All Together
Here's how you'd evaluate everything in one place:
async function initFeatureFlags(user) {
const flags = await loadFlags();
const context = getFlagContext(user);
return evaluateFlags(flags, context);
}
And in React or Vue, wrap this in a provider or global state store for use across components.
✅ Benefits:
- 🎯 Flags adapt to users, time, and environment
- 🔒 No client secrets exposed — only use safe runtime context
- 🧰 Easy to test and debug — expressions are just strings
- 🛡️ Resilient to backend/API/CDN failures
⚠️ Caveats:
- 🐛 Poorly written expressions can fail silently — add logging!
- 🔄 Live updates require polling or refresh
- ⚠️ Avoid user-controlled inputs in expressions to prevent injection
Summary
Static flags are easy, but limited. With expression-based, context-aware flags, you unlock smarter behavior across different users, regions, and environments — all with just JSON and a little JS.
Want to get really good at this? My full guide shows how to:
- Write safe, expressive flag logic with JavaScript
- Handle gradual rollouts and context-based targeting
- Version and migrate flags over time
- Build resilient, offline-capable flag systems for production
Feature Flag Engineering Like a Pro: From JS Expressions to Global Rollouts — just $10 on Gumroad, packed with patterns and code samples.
If this was helpful, you can also support me here: Buy Me a Coffee ☕