Flaggy
Get started
← Blog

How to use feature flags in JavaScript and TypeScript

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.