Skip to main content

Best Feature Flag Libraries for JavaScript in 2026

·PkgPulse Team
0

TL;DR

OpenFeature + a provider for standards-based flagging; LaunchDarkly for enterprise; Unleash for self-hosted control. OpenFeature (~200K weekly downloads) is the CNCF standard that decouples your code from any vendor. LaunchDarkly (~400K downloads) is the SaaS leader with best-in-class targeting. Unleash (~150K downloads) is the leading open-source option — self-hosted, no vendor lock-in. For new projects, code against OpenFeature so you can swap providers freely.

Key Takeaways

  • LaunchDarkly: ~400K weekly downloads — SaaS leader, 99.99% uptime SLA, streaming updates
  • OpenFeature: ~200K downloads — CNCF standard, vendor-neutral API, provider plugins
  • Unleash: ~150K downloads — open-source, self-hosted, full feature management
  • Flagsmith: ~80K downloads — open-source + SaaS, simpler than Unleash
  • OpenFeature — code once, swap providers; LaunchDarkly, Unleash, Flagsmith all have OpenFeature providers

OpenFeature (The Standard)

// OpenFeature — vendor-neutral feature flag standard
import { OpenFeature } from '@openfeature/server-sdk';

// Register a provider (swap this to change vendors)
import { LaunchDarklyProvider } from '@openfeature/launchdarkly-provider';
// Or: import { UnleashProvider } from '@openfeature/unleash-provider';
// Or: import { FlagsmithProvider } from '@openfeature/flagsmith-provider';
// Or: import { InMemoryProvider } from '@openfeature/server-sdk'; // for tests

await OpenFeature.setProviderAndWait(
  new LaunchDarklyProvider(process.env.LD_SDK_KEY!)
);

const client = OpenFeature.getClient();

// Boolean flag
const newCheckoutEnabled = await client.getBooleanValue(
  'new-checkout-flow',
  false,  // default value if flag missing
);

// String flag (variants)
const checkoutVariant = await client.getStringValue(
  'checkout-variant',
  'control',
);

// Number flag
const maxRetries = await client.getNumberValue('max-retries', 3);

// Object flag (JSON)
const config = await client.getObjectValue('feature-config', {});
// OpenFeature — evaluation context (targeting)
import { OpenFeature, EvaluationContext } from '@openfeature/server-sdk';

const client = OpenFeature.getClient();

// Per-request context for targeting rules
const context: EvaluationContext = {
  targetingKey: user.id,         // Used for % rollouts
  attributes: {
    email: user.email,
    plan: user.plan,             // 'free' | 'pro' | 'enterprise'
    country: user.country,
    betaTester: user.betaTester,
  },
};

// Flag evaluated with targeting context
const enabled = await client.getBooleanValue(
  'pro-dashboard',
  false,
  context
);

// Result: true for pro/enterprise users, false for free
// OpenFeature — React with hooks
import { OpenFeatureProvider, useBooleanFlagValue } from '@openfeature/react-sdk';
import { OpenFeature } from '@openfeature/web-sdk';
import { LaunchDarklyClientProvider } from '@openfeature/launchdarkly-client-provider';

// Setup (client-side SDK)
await OpenFeature.setProviderAndWait(
  new LaunchDarklyClientProvider(
    process.env.NEXT_PUBLIC_LD_CLIENT_ID!,
    { user: { key: userId } }
  )
);

// Provider wraps your app
function App() {
  return (
    <OpenFeatureProvider>
      <Dashboard />
    </OpenFeatureProvider>
  );
}

// Hooks in components
function Dashboard() {
  const newLayout = useBooleanFlagValue('new-dashboard-layout', false);
  const variant = useStringFlagValue('dashboard-variant', 'v1');

  return newLayout ? <NewDashboard variant={variant} /> : <OldDashboard />;
}
// OpenFeature — in-memory provider for tests (no external deps)
import { InMemoryProvider, OpenFeature } from '@openfeature/server-sdk';

const flagConfig = {
  'new-checkout-flow': {
    defaultVariant: 'on',
    variants: { on: true, off: false },
  },
  'checkout-variant': {
    defaultVariant: 'test-variant',
    variants: { control: 'control', 'test-variant': 'B' },
  },
};

OpenFeature.setProvider(new InMemoryProvider(flagConfig));
// Now tests run without network calls or SDK keys

LaunchDarkly (Enterprise SaaS)

// LaunchDarkly — server-side SDK
import * as ld from '@launchdarkly/node-server-sdk';

const client = ld.init(process.env.LD_SDK_KEY!, {
  // Optional: polling fallback if streaming fails
  stream: true,
  // Optional: private attributes (not sent to LD analytics)
  allAttributesPrivate: false,
  privateAttributes: ['email'],
});

await client.waitForInitialization({ timeout: 5 });

// Evaluate with user context
const context: ld.LDContext = {
  kind: 'user',
  key: user.id,
  name: user.name,
  email: user.email,
  custom: {
    plan: user.plan,
    country: user.country,
  },
};

// Boolean flag
const enabled = await client.variation('dark-mode', context, false);

// String flag
const theme = await client.variation('ui-theme', context, 'default');

// Flag with detailed reason (for debugging)
const detail = await client.variationDetail('new-feature', context, false);
console.log(detail.reason); // { kind: 'RULE_MATCH', ruleIndex: 0 }
// LaunchDarkly — multi-context (user + organization)
const context: ld.LDContext = {
  kind: 'multi',
  user: {
    kind: 'user',
    key: user.id,
    email: user.email,
  },
  organization: {
    kind: 'organization',
    key: org.id,
    name: org.name,
    plan: org.plan,           // Target by org plan
  },
};

// Flag can now target by user OR org attributes
const enabled = await client.variation('org-feature', context, false);

Best for: Enterprise teams needing 99.99% SLA, advanced targeting, A/B testing integration, audit logs.


Unleash (Self-Hosted Open Source)

// Unleash — self-hosted Node.js SDK
import { initialize, isEnabled } from 'unleash-client';

initialize({
  url: 'https://unleash.yourcompany.com/api',
  appName: 'your-app',
  customHeaders: { Authorization: process.env.UNLEASH_API_TOKEN! },
});

// Simple boolean check
if (isEnabled('new-checkout', { userId: user.id })) {
  // Use new checkout
}
// Unleash — full client with context
import Unleash from 'unleash-client';

const unleash = new Unleash({
  url: 'https://unleash.yourcompany.com/api',
  appName: 'pkgpulse',
  customHeaders: { Authorization: process.env.UNLEASH_API_TOKEN! },
  // Metrics reported to Unleash dashboard
  metricsInterval: 60_000,
  // Cache flags locally for 15s
  refreshInterval: 15_000,
});

await unleash.start();

const context = {
  userId: user.id,
  sessionId: req.sessionId,
  remoteAddress: req.ip,
  properties: {
    plan: user.plan,
    country: user.country,
  },
};

// Strategy: gradual rollout to 20% of users
const enabled = unleash.isEnabled('beta-dashboard', context);

// Variant (A/B)
const variant = unleash.getVariant('checkout-variant', context);
// variant.name = 'control' | 'A' | 'B'
// variant.payload.value = custom JSON

// Cleanup
process.on('SIGTERM', () => unleash.destroy());
// Unleash — Next.js integration
// lib/unleash.ts (server-side only)
import { createUnleash } from 'unleash-client';

let unleashInstance: Unleash;

export function getUnleash() {
  if (!unleashInstance) {
    unleashInstance = createUnleash({
      url: process.env.UNLEASH_URL!,
      appName: 'pkgpulse',
      customHeaders: { Authorization: process.env.UNLEASH_API_TOKEN! },
    });
  }
  return unleashInstance;
}

// In API route or server component
export async function GET(req: Request) {
  const unleash = getUnleash();
  const enabled = unleash.isEnabled('new-api', {
    userId: getUserId(req),
  });

  return Response.json({ enabled });
}

Best for: Teams that need full control over flag data, no vendor lock-in, or have compliance requirements preventing SaaS.


Flagsmith (Simpler Self-Hosted)

// Flagsmith — simpler API, cloud + self-hosted
import Flagsmith from 'flagsmith-nodejs';

const flagsmith = new Flagsmith({
  environmentKey: process.env.FLAGSMITH_ENV_KEY!,
  // self-hosted: apiUrl: 'https://flagsmith.yourcompany.com/api/v1'
});

// Get all flags for a user
const flags = await flagsmith.getIdentityFlags(user.id, {
  traits: {
    plan: user.plan,
    email: user.email,
  },
});

// Check feature
const enabled = flags.isFeatureEnabled('dark-mode');

// Get remote config value
const theme = flags.getFeatureValue('theme-config');
// Returns the value set in the Flagsmith UI (string, number, JSON)

Comparison Table

ToolTypePricingTargetingSDK QualitySelf-Hosted
OpenFeatureStandard (no backend)FreeVia provider✅ ExcellentN/A
LaunchDarklySaaS$10+/seat✅ Advanced✅ Excellent
UnleashOSS + SaaSFree (self-hosted)✅ Good✅ Good
FlagsmithOSS + SaaSFree (self-hosted)✅ Basic✅ Good
GrowthBookOSS + SaaSFree (self-hosted)✅ A/B focused✅ Good

When to Choose

ScenarioPick
Any new projectOpenFeature SDK + any provider
Enterprise, needs SLA + advanced targetingLaunchDarkly
Full data ownership, complianceUnleash (self-hosted)
Simple flags, wants self-hosted optionFlagsmith
A/B testing + feature flags combinedGrowthBook
Testing in CI without external depsOpenFeature InMemoryProvider
Migrating vendors without code changesOpenFeature

Compare feature flag package health on PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.