Feature flag systems often rely on dynamic logic — "enable this for logged-in users", "disable for legacy browsers", etc. Some teams use simple booleans. Others reach for configuration platforms that support expression-based evaluation. But what if you want full control?
In this post, you’ll learn how to safely evaluate feature flag expressions in JavaScript, without exposing your app to security risks or runtime failures.
Step 1: Accept Expressions as Strings (Cautiously)
You might store your flag definitions like this:
const flagConfig = {
betaDashboard: "user.role === 'beta' && user.isActive",
enableGifts: "context.env === 'production' && getBucket(user.id) < 50",
};
The power of expressions is flexibility: they can target users, environments, percentages, etc.
Step 2: Create a Controlled Evaluation Environment
To avoid security issues, don’t eval()
blindly. Instead, use Function
to define a scoped evaluator:
function evaluateFlags(flagDefs, user, context = {}) {
const results = {};
for (const [key, expr] of Object.entries(flagDefs)) {
try {
const fn = new Function('user', 'context', 'getBucket', `return (${expr})`);
results[key] = !!fn(user, context, getBucket);
} catch (err) {
console.error(`Error evaluating flag "${key}":`, err);
results[key] = false;
}
}
return results;
}
This restricts what can be accessed inside the flag logic. You control the available functions (getBucket
, etc).
Step 3: Provide Helper Functions Like getBucket()
If you want gradual rollouts or segmenting by percentage, include helpers like this:
function getBucket(id) {
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = (hash << 5) - hash + id.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash % 100); // 0–99
}
This allows expressions like:
"getBucket(user.email) < 20"
...to target a consistent 20% of users.
Step 4: Validate or Restrict Expressions (Optional)
For added safety, consider linting or validating expressions at build time. You can:
- Disallow certain keywords (
window
,document
) - Limit expression length
- Require static analysis before deployment
This helps catch dangerous or broken logic before it ships.
Step 5: Pre-Evaluate and Persist Flags
To avoid re-evaluating expressions every render, evaluate once and cache the result:
async function initFeatureFlags(user, context) {
const flags = evaluateFlags(flagConfig, user, context);
localStorage.setItem('evaluatedFlags', JSON.stringify(flags));
return flags;
}
Later, just read from localStorage
or context provider.
✅ Pros:
- ✨ Supports expressive, dynamic logic per user/session
- 🧪 Enables A/B tests, environment targeting, and phased rollouts
- 🔐 Safer than
eval()
when used with a scoped function - ⚡ Evaluated once and cached for fast reuse
⚠️ Cons:
- 🧠 Requires discipline: miswritten expressions can break features
- 🔍 Security is your responsibility — restrict scope tightly
- 🧹 Debugging logic bugs in expressions can be tricky
Summary
Evaluating feature flag expressions in JavaScript gives you flexibility and power — if done safely. By scoping your evaluation function, restricting inputs, and caching results, you can support rich logic for flag targeting without compromising your app’s stability.
Want to go deeper? 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 ☕