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