A practical guide to implementing feature flags in JS/TS: what they are, when to use them, and how to avoid the traps that make them painful to manage.
Feature flags are a conditional: if (flagEnabled) { show new thing } else { show old thing }. That’s the entire concept. The value comes from where the condition is evaluated and who controls it — not the developer at deploy time, but a dashboard at runtime.
This guide covers the core patterns, when each one applies, and what separates a well-managed flag from one that quietly rots in your codebase.
The basic pattern
A feature flag SDK gives you a function. You call it with a flag key, optionally a user context, and it returns a boolean (or a variant, for multivariate flags).
import { flaggy } from '@flaggy.io/sdk-js';
const client = flaggy({ apiKey: process.env.FLAGGY_API_KEY });
await client.initialize();
if (client.isEnabled('release-checkout-flow', { key: user.id })) {
return <NewCheckout />;
}
return <LegacyCheckout />;
The SDK downloads your ruleset on initialization and evaluates locally. No network call happens at the point of evaluation — it’s a dictionary lookup against rules in memory. This is why MAU-based pricing from some vendors doesn’t make technical sense: the vendor’s infrastructure isn’t involved when a flag evaluates.
When to use a feature flag
Release control is the most common case. You merge a half-finished feature to main behind a flag, deploy continuously, and flip the flag when it’s ready. No long-lived feature branches, no merge conflicts.
Percentage rollouts let you ship to 5% of users, watch your error rate, and expand if it looks clean. This is a canary deployment without the infrastructure complexity of actually routing traffic.
User targeting lets you ship to internal users, beta testers, or a specific customer segment first. You define a segment in the dashboard — “users where plan = enterprise” — and enable the flag for that segment.
Kill switches are flags you never intend to remove. A circuit breaker on a third-party integration, a way to disable a feature if it’s causing support volume. Having this in a dashboard is faster than a deploy.
The initialization pattern
Initialize the SDK once at app startup and share the client. Don’t initialize per-request or per-component — it defeats the local evaluation model.
// flaggy.ts — initialize once, export everywhere
import { flaggy } from '@flaggy.io/sdk-js';
export const flags = flaggy({
apiKey: process.env.FLAGGY_API_KEY!,
});
await flags.initialize();
// any component or module
import { flags } from './flaggy';
const showFeature = flags.isEnabled('my-feature', { key: currentUser.id });
Targeting rules
Most SDKs let you pass a context object — any key/value pairs you want to use for targeting. The dashboard lets you define rules against those attributes without a code change.
Common context attributes:
flags.isEnabled('experiment-dark-mode', {
key: user.id,
email: user.email,
plan: user.plan, // target by billing tier
accountAge: daysSinceSignup,
country: user.countryCode,
});
You define the rule once in the dashboard: “users where plan = team AND accountAge > 30”. No deploy required to change targeting.
The cleanup problem
The real cost of feature flags isn’t evaluating them — it’s the ones you forget to remove. A codebase with 200 permanent flags is harder to reason about than one with 20.
A few habits that help:
Add a ticket at flag creation. When you create a flag, immediately create a ticket to remove it after the rollout is done. The flag has a lifespan of “rollout + one release cycle.”
Name flags by lifecycle prefix. Use a prefix that encodes intent: release-new-dashboard, experiment-checkout-button, kill-switch-payment-provider, ops-cache-ttl. It makes lifespan visible at a glance — you can immediately separate flags that need cleanup from ones that are permanent.
Review old flags quarterly. Pull a list of flags that haven’t changed in 90 days. Most of them are either fully rolled out (safe to remove) or forgotten experiments.
Server-side vs client-side evaluation
Some teams evaluate flags server-side to avoid any flag state being visible in the client bundle. The tradeoff:
Client-side: zero latency at evaluation, no server dependency, flag rules are technically visible in the bundle (usually fine for most use cases)
Server-side: rules stay private, adds one lookup per request (fast if in-memory, latency concern if remote)
For most product flags — rollouts, A/B tests, kill switches — client-side evaluation is fine. For flags that gate access control or pricing, server-side is worth the overhead.
What to look for in a flag platform
You need: a dashboard to manage flags without a code change, targeting rules, audit history (who changed what and when), and an SDK that evaluates locally.
What you don’t need to pay for: MAU metering. Modern SDKs evaluate client-side. The vendor’s servers aren’t involved in evaluations — if you’re paying per-MAU, you’re paying for a proxy metric that doesn’t reflect actual vendor cost.
Flaggy’s Team plan is $99/month flat — unlimited seats, no MAU fees, full flag analytics and audit log included.
Top comments (0)