Skip to main content

Best Feature Flag Libraries for JavaScript in 2026

·PkgPulse Team

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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.