Skip to main content

Guide

Feature Flags 2026: LaunchDarkly vs PostHog Guide

Compare LaunchDarkly vs PostHog vs custom feature flags for SaaS in 2026. Targeting rules, pricing, A/B testing, and which boilerplates include flags out.

StarterPick Team

TL;DR

PostHog for most indie SaaS — generous free tier (1M events/month), combines analytics + feature flags + session recording in one platform. LaunchDarkly for enterprise SaaS where feature flags are a core product requirement (targeting rules, experiments, audit logs). Custom implementation for simple on/off flags without targeting rules. PostHog replaces 3-4 separate tools.

Why Feature Flags Matter

Feature flags decouple deployment from release:

Without flags:        Deploy → Feature is live for everyone
With flags:           Deploy → Feature off → Gradually enable by segment
                               (internal testers → beta users → 10% → 50% → 100%)

Use cases:

  • Canary releases — Roll out to 5% of users, monitor metrics, then expand
  • Beta access — "Pro plan users get early access to this feature"
  • A/B testing — Show variant A to 50%, variant B to 50%, measure conversion
  • Kill switches — Instantly disable a broken feature without redeployment
  • Gradual rollout — Region by region, or cohort by cohort

PostHog: Analytics + Flags + Session Recording

PostHog combines analytics, feature flags, session recording, and A/B testing in one platform. The free tier is generous enough for most indie SaaS.

// lib/posthog.ts
import PostHog from 'posthog-node';

export const posthog = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
  host: process.env.NEXT_PUBLIC_POSTHOG_HOST!,
});

// Server-side flag evaluation
export async function isFeatureEnabled(
  flag: string,
  userId: string,
  properties?: Record<string, any>
): Promise<boolean> {
  return posthog.isFeatureEnabled(flag, userId, { personProperties: properties });
}
// Client-side flag usage
'use client';
import { useFeatureFlagEnabled } from 'posthog-js/react';

export function Dashboard() {
  const isNewDashboardEnabled = useFeatureFlagEnabled('new-dashboard-v2');

  return (
    <div>
      {isNewDashboardEnabled ? (
        <NewDashboardLayout />  // Beta feature
      ) : (
        <OldDashboardLayout />  // Stable fallback
      )}
    </div>
  );
}
// Server Component flag check
import { isFeatureEnabled } from '@/lib/posthog';

async function BillingPage({ userId }: { userId: string }) {
  const [isAnnualPlanEnabled, user] = await Promise.all([
    isFeatureEnabled('annual-billing', userId),
    getUser(userId),
  ]);

  return (
    <div>
      <MonthlyPlan />
      {isAnnualPlanEnabled && <AnnualPlan />}  // Only show to flag-enabled users
    </div>
  );
}

PostHog Flag Targeting Rules

PostHog allows complex targeting via their UI:

Flag: "new-ai-features"
├── Enable for: users where plan = "pro" (100%)
├── Enable for: users where email ends in @yourcompany.com (100%)
├── Enable for: users where created_at > 2025-01-01 (50%)
└── Everyone else: false

No code changes needed to adjust rollout percentage or targeting rules.


LaunchDarkly: Enterprise Feature Management

LaunchDarkly is purpose-built for feature flags at enterprise scale. Rich SDK, audit logging, RBAC for flag management, unlimited targeting complexity.

import LaunchDarkly from '@launchdarkly/node-server-sdk';

const ldClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
await ldClient.waitForInitialization();

// Context-based evaluation (LaunchDarkly v6+)
async function checkFlag(flagKey: string, userId: string, orgId: string) {
  const context = {
    kind: 'multi',
    user: { key: userId, plan: 'pro' },
    organization: { key: orgId, tier: 'enterprise' },
  };

  return ldClient.variation(flagKey, context, false);
}

// Flag with JSON payload (return config objects, not just booleans)
const searchConfig = await ldClient.variation(
  'search-algorithm-config',
  context,
  { algorithm: 'keyword', maxResults: 10 }  // Default
);
// searchConfig could be: { algorithm: 'semantic', maxResults: 25, model: 'v3' }

LaunchDarkly Advantages

  • Experiments: Proper A/B testing with statistical significance
  • Scheduled releases: Set a date/time to automatically enable a flag
  • Audit trail: Every flag change logged with who changed it and when
  • RBAC: Different teams can manage different flags
  • JSON flags: Return complex configuration objects, not just booleans

LaunchDarkly Pricing

Starter: $10/seat/month. For a 3-person team: $30/month. Reasonable for enterprise B2B SaaS where flag management is a business requirement.


Custom Feature Flags (Simplest Option)

For basic on/off flags without targeting rules:

// lib/features.ts — simplest possible feature flags
const features = {
  NEW_BILLING_UI: process.env.FEATURE_NEW_BILLING_UI === 'true',
  AI_ASSISTANT: process.env.FEATURE_AI_ASSISTANT === 'true',
  BETA_API: process.env.FEATURE_BETA_API === 'true',
} as const;

export function isEnabled(feature: keyof typeof features): boolean {
  return features[feature];
}
import { isEnabled } from '@/lib/features';

function Settings() {
  return (
    <div>
      {isEnabled('NEW_BILLING_UI') && <NewBillingSettings />}
    </div>
  );
}

When this is enough:

  • Internal flags (dev vs prod differences)
  • Simple on/off without per-user targeting
  • Early stage with no need for gradual rollout

When to upgrade to PostHog/LaunchDarkly:

  • Need per-user or per-plan targeting
  • Need gradual percentage rollout
  • Need to track which users saw which version

Boilerplate Feature Flag Support

BoilerplateFeature FlagsProvider
ShipFast
Supastarter
Makerkit✅ OptionalPostHog
T3 Stack
Open SaaSPostHog


Implementing Flags in Next.js App Router

Server Components add complexity to feature flags — the hook-based API doesn't work server-side:

// lib/flags.ts — works in Server Components and Route Handlers
import { PostHog } from 'posthog-node';

const posthog = new PostHog(process.env.POSTHOG_KEY!);

// Server-side evaluation — for Server Components
export async function getServerFlag(
  flagKey: string,
  userId: string,
  properties?: Record<string, string>
): Promise<boolean> {
  return await posthog.isFeatureEnabled(flagKey, userId, {
    personProperties: properties,
  }) ?? false;
}
// app/dashboard/page.tsx — Server Component
import { getServerFlag } from '@/lib/flags';
import { auth } from '@/auth';

export default async function DashboardPage() {
  const { user } = await auth();
  const newDashboard = await getServerFlag('new-dashboard-v2', user.id, {
    plan: user.plan,
    company: user.company,
  });

  return newDashboard ? <NewDashboard /> : <OldDashboard />;
}

For Client Components, use PostHog's React hook:

// Client Component — hook-based
'use client';
import { useFeatureFlagEnabled } from 'posthog-js/react';

export function BillingSection() {
  const showAnnualPlans = useFeatureFlagEnabled('annual-billing-v2');

  return (
    <div>
      <MonthlyPlan />
      {showAnnualPlans && <AnnualPlan discount="20%" />}
    </div>
  );
}

Flag Strategy: What to Flag vs What Not to

Not every code change should be behind a flag. Over-flagging creates technical debt — dead flag branches accumulate until someone audits and cleans them up.

Good candidates for flags:

  • Major UI redesigns (new dashboard, new checkout flow)
  • Experimental features with uncertain user response
  • Features requiring gradual rollout due to infrastructure impact
  • Beta access for specific customer cohorts

Poor candidates for flags:

  • Bug fixes (just deploy them)
  • Internal refactors (no user-visible change)
  • Features that work or don't work — no partial state
  • One-time migrations

Flag lifecycle management: Set a removal deadline when you create a flag. A flag rolled out to 100% of users should be cleaned up (code merged, flag deleted) within 2-4 weeks. Old flags become confusing — was new-billing-ui the 2024 redesign or the 2025 one?


LaunchDarkly vs PostHog: The Cost Comparison

UsersLaunchDarkly (3 seats)PostHog
0-10K events$30/monthFree
100K events/month$30/monthFree
1M events/month$30/monthFree
10M events/month$30/month~$150/month

For most indie SaaS products under 1M events/month, PostHog's free tier covers both analytics and feature flags. LaunchDarkly makes sense when your organization has multiple teams managing different feature flags and needs the RBAC, audit trail, and scheduled release features.


Key Takeaways

  • PostHog is the right choice for most indie SaaS — 1M free events/month covers analytics + flags + session recording
  • LaunchDarkly is justified for enterprise B2B where feature flag management is a business process (scheduled releases, RBAC, audit trails)
  • Custom env-var flags are sufficient for early-stage products with simple on/off needs and no per-user targeting
  • Feature flags in Server Components require the Node.js SDK (posthog-node); client components use the React hook
  • Flag cleanup is as important as flag creation — remove flags that are 100% rolled out within 2-4 weeks

Gradual Rollout Patterns

The power of feature flags is not just on/off — it's gradual rollout with measurement:

Percentage rollout: Enable for 10% of users to validate behavior before expanding.

Cohort rollout: Enable for users on Pro plan first, then Starter, then Free.

User property targeting: Enable for users where company_size > 50 (enterprise beta).

// PostHog gradual rollout — no code change needed
// Just adjust in the PostHog dashboard:
// Flag: "new-checkout-flow"
// Rules: 10% of users → 25% → 50% → 100%

// Your code just reads the flag:
const useNewCheckout = await posthog.isFeatureEnabled('new-checkout-flow', userId);

The rollout percentage is changed in the PostHog or LaunchDarkly dashboard — no code deployment required. Rollback means setting the percentage to 0%, not reverting code.


Connecting Flags to Metrics

Feature flags are most valuable when connected to your product metrics. The pattern:

  1. Enable a flag for 10% of users
  2. Track a conversion metric (onboarding completion, upgrade, retention)
  3. Compare flag-exposed users to control group
  4. Roll out to 100% if the metric improves, kill if it doesn't

PostHog makes this connection native — the same tool tracks both the flag assignment and the conversion event:

Experiment: "streamlined-onboarding-v2"
Control (90%):   Onboarding completion: 42%
Test (10%):      Onboarding completion: 58%
Statistical significance: 95%
→ Roll out to 100%

LaunchDarkly requires exporting data to a separate analytics tool for this analysis. PostHog's integrated approach is a concrete advantage for smaller teams without dedicated data infrastructure.


When to Introduce Feature Flags

Most indie SaaS products don't need feature flags on day one. The right time to introduce them is when:

  • You're shipping a significant UI change to an existing user base (don't break what's working)
  • You have enough users (100+) to run meaningful A/B tests
  • You need beta access for specific customers before general availability
  • You've had an incident caused by deploying a broken feature to all users simultaneously

Before that point, deploying directly to production and monitoring error rates in Sentry is simpler. Feature flags add infrastructure complexity — connection latency on each flag evaluation (PostHog caches flags client-side to minimize this), fallback behavior when the flag service is down, and the ongoing maintenance cost of old flag cleanup. For a product with fewer than 50 active users, the complexity isn't worth the benefit. Feature flags are a productivity investment that pays off as your user base grows and the cost of a bad deploy increases.

For most SaaS products, PostHog's integrated flags + analytics wins over LaunchDarkly's more powerful but expensive feature management platform. LaunchDarkly's workflow features (approval flows, scheduled rollouts, audit history) justify the cost for enterprise teams deploying dozens of features in parallel across hundreds of thousands of users. For solo founders and small teams, PostHog's free tier and integrated analytics cover 90% of flag use cases without the operational overhead.


Testing With Feature Flags: Avoiding False Confidence

Feature flags introduce a testing dimension that most development workflows do not account for. When a flag controls code paths, your test suite must exercise both the flag-enabled and flag-disabled paths. A test suite that only tests the "flag on" state will not catch regressions in the "flag off" path — and the "flag off" path is what your existing users experience until you roll out to 100%.

The testing pattern for flagged code in a Next.js boilerplate: mock the flag evaluation in unit and integration tests to explicitly test both paths. With PostHog, the useFeatureFlagEnabled hook can be mocked in Jest or Vitest to return true for one test suite and false for another. With LaunchDarkly, the SDK ships a test client that allows setting flag values directly. Write at least one test case for each path: the feature-enabled path works correctly, and the feature-disabled (fallback) path works correctly. This doubles the number of test cases for flagged features but is essential for maintaining confidence during gradual rollouts.

End-to-end tests with Playwright or Cypress face a harder problem: the flag evaluation happens either in the browser (for client-side evaluation) or on the server (for SSR evaluation), and test environments may not have access to the same flag configuration as production. The standard approach is to seed the test environment with known flag states using the provider's testing utilities before each test run. PostHog has a testing mode; LaunchDarkly has a relay proxy that can be configured for predictable test behavior.

Integrating Feature Flags Into Your Boilerplate

Most SaaS boilerplates ship without any feature flag infrastructure. Adding PostHog to an existing Next.js boilerplate takes about 30 minutes and gives you flags plus analytics in one integration. The typical Next.js boilerplate setup looks like this: install posthog-js and posthog-node, wrap your root layout with the PostHog provider, and create a server-side client for use in Server Components and API routes. The client-side hook gives React components access to flag values; the server-side client evaluates flags during SSR so users never see a flash of unprotected content.

For teams using a boilerplate with Supabase, PostHog connects neatly through user IDs — call posthog.identify(userId) after authentication and every subsequent flag evaluation and analytics event carries the user context. This is how you build the cohort rollouts described above: create a PostHog cohort of "Pro plan users" using a plan user property, then target your feature flag at that cohort. No code change is needed to adjust the targeting — just update the cohort definition in the dashboard.

LaunchDarkly's integration requires a bit more ceremony: the SDK initialization, a provider component, and separate setup for SSR contexts. For boilerplates that already include a providers abstraction file, this adds one to two hours of integration work. The upside is LaunchDarkly's flag evaluation is slightly faster for complex rule sets because flags are evaluated locally against a streamed rule set rather than fetched on each evaluation.

The practical advice: wire up PostHog early. Even if you do not use feature flags immediately, having the analytics integration in place means you can add your first flag in five minutes when you are ready to ship a risky change. Waiting until you "need" flags means adding infrastructure under pressure rather than proactively. For teams evaluating a complete boilerplate setup, see the best SaaS boilerplates guide — several include PostHog pre-wired.

Operational Considerations: Flag Lifecycle Management

Feature flags create operational debt if not managed actively. Teams that add flags without a removal process end up with a codebase full of conditionals for experiments that shipped months ago. The standard approach is to treat flag cleanup as a first-class engineering task.

Naming conventions help: prefix flags with a type indicator — exp- for experiments (A/B tests), release- for gradual rollouts, ops- for operational kill switches. This makes it immediately clear which flags are temporary and which are permanent infrastructure. The prefix also helps when searching the codebase for all active flags during a quarterly cleanup sweep.

When you create a release flag, set a calendar reminder three weeks out to remove it once you have confirmed 100% rollout is stable. PostHog does not enforce expiry dates, but LaunchDarkly's enterprise plan includes flag expiry warnings. For most indie teams, a calendar reminder is sufficient. Before removing a flag, search your codebase for every reference to the flag key. Remove the conditional and leave only the code for the winning path — a flag that has been at 100% for two weeks means the old code path is dead, and deleting it reduces cognitive load for anyone reading that component in the future.

PostHog's free tier supports unlimited flags. LaunchDarkly's free tier supports 5 flags — enough to prove the concept but not a production limit. If you hit LaunchDarkly's limits before deciding to pay, migrate the overflow flags to environment variables (for simple booleans) or PostHog (for anything with user targeting). Running two flag systems simultaneously is messy; commit to one before your flag count grows. For a practical walk-through of setting up analytics and feature flags in a Next.js production app, the production SaaS free tools guide covers the zero-cost stack in detail.


Find boilerplates with analytics and feature flags in the StarterPick directory.

See our best SaaS boilerplates guide for the full comparison including observability and monitoring features.

Browse best Next.js boilerplates for starters with pre-wired analytics integrations.

Check out this starter

View PostHogon StarterPick →

The SaaS Boilerplate Matrix (Free PDF)

20+ SaaS starters compared: pricing, tech stack, auth, payments, and what you actually ship with. Updated monthly. Used by 150+ founders.

Join 150+ SaaS founders. Unsubscribe in one click.