Most feature flag systems are overkill — expensive, SDK-heavy, or slow to evaluate. But what if you could create your own lightweight, real-time flag engine using plain JavaScript expressions, evaluated directly at the edge or client?

This article shows how to build a no-dependency feature flag system that:

  • Stores rules as simple strings (like "user.country === 'US'")
  • Evaluates flags at runtime using Function()
  • Runs client-side, in middleware, or at the CDN edge
  • Is composable, testable, and fast

Step 1: Define Flag Rules as Strings

Feature flags are just conditional logic. Instead of DSLs or SDKs, we use plain JavaScript strings:


const featureFlags = {
  'newCheckout': "user.country === 'US' && user.beta === true",
  'darkMode': "user.preferences.includes('dark')",
};

These strings are tiny, cacheable, and human-readable.


Step 2: Evaluate Flags with Function Safely

Use the Function constructor to evaluate each flag. It’s safer than eval, especially if isolated.


function evaluateFlags(flags, user) {
  const results = {};
  for (const [key, expr] of Object.entries(flags)) {
    try {
      const fn = new Function('user', `return (${expr})`);
      results[key] = !!fn(user);
    } catch (err) {
      results[key] = false;
    }
  }
  return results;
}

Usage:


const user = { country: 'US', beta: true, preferences: ['dark'] };
const activeFlags = evaluateFlags(featureFlags, user);

Step 3: Edge-Local Flag Evaluation (e.g. Cloudflare Workers)

This approach is perfect for edge environments, like Cloudflare Workers or Vercel Middleware, where latency and runtime size matter.

Example in a Cloudflare Worker:


addEventListener('fetch', event => {
  const user = parseUserFromRequest(event.request);
  const flags = evaluateFlags(featureFlags, user);

  if (flags.newCheckout) {
    return fetch('https://example.com/new-checkout');
  } else {
    return fetch('https://example.com/old-checkout');
  }
});

No external requests, <1ms eval time, and works globally.


Step 4: Add Percentage Rollouts

Support gradual rollouts using a hash function and user ID:


function getBucket(userId) {
  let hash = 0;
  for (let i = 0; i < userId.length; i++) {
    hash = ((hash << 5) - hash) + userId.charCodeAt(i);
    hash |= 0;
  }
  return Math.abs(hash % 100);
}

Use this inside your expression:


const featureFlags = {
  'newFeature': "getBucket(user.id) < 10"
};

Pass getBucket into the context by modifying the Function call:


const fn = new Function('user', 'getBucket', `return (${expr})`);
results[key] = !!fn(user, getBucket);

Step 5: Serialize and Serve Flags from JSON

Flags can live in versioned JSON, hosted on your CDN:


{
  "darkMode": "user.preferences.includes('dark')",
  "searchV2": "getBucket(user.id) < 5 && user.plan === 'pro'"
}

Fetch this at startup or deploy-time, and use across frontend, backend, and edge consistently.


Pros:

  • ⚡ Tiny runtime, no external dependencies
  • 🌍 Works at edge, client, or server equally
  • 🧠 Uses plain JS — no DSL to learn
  • 💰 Avoids expensive feature flag SaaS
  • 🧪 Fully testable and snapshot-friendly

⚠️ Cons:

  • 🔐 Must sandbox expressions if user-generated
  • 🔍 Debugging dynamic expressions is trickier
  • 🔄 No built-in A/B analytics — must wire yourself
  • ❗ Expression injection risk if rules are untrusted

Summary

You don’t need LaunchDarkly or heavyweight tooling to manage feature flags. With simple JavaScript expressions and Function(), you can build a fast, flexible, and deployment-friendly flag engine that runs anywhere — edge, client, server — without depending on a single third-party service.

This lets you ship faster, test safer, and keep control of your architecture.


Want a real deep dive into this? My full guide explains in detail how to:

  • Use JavaScript expressions for safe feature flag evaluation
  • Handle gradual feature rollouts and exposure
  • Implement flag versioning, migration strategies, and more
  • Design a feature flagging system that works offline and is resilient to failure

Feature Flag Engineering Like a Pro: From JS Expressions to Global Rollouts — just $10 on Gumroad.

If this was helpful, you can also support me here: Buy Me a Coffee