Skip to content

Product Engineering

Feature Flags in 2026 — LaunchDarkly vs PostHog vs Self-Host in Supabase

Feature flags let you ship code that's off by default, then turn it on gradually. Control group sees the old behavior. 10% of users see the new feature. No rollback needed — you just flip the flag. LaunchDarkly is the enterprise standard at $99/seat/month, which is fine if your company has money. PostHog includes flags if you're already using it for analytics. Statsig is free at scale and solid. But for most product teams, the real play is rolling your own: a `features` table in Supabase, a client library that evaluates flags against Postgres rules, and a gradual rollout strategy that ships code with feature flags instead of long-lived branches. Takes a day, costs nothing, gives you full control. This post covers the definition, why flags matter, all 4 approaches (LaunchDarkly, PostHog, Statsig, DIY), the DIY Supabase pattern you can copy, production rollout strategies, 6 FAQs, and the verdict.

🚀 🎚️ ⚙️

You're shipping a new checkout flow. You've tested it locally. You're confident it works. You merge to main. You deploy. 2% of your users hit a bug you didn't catch. Revenue tanks for 20 minutes. You rollback. You spend the next day debugging. You deploy again. This is the status quo.

Or: you ship the code behind a feature flag. It's off by default. You turn it on for 1% of users. Nothing breaks. You watch the logs. After 2 hours, you flip it to 10%. After a day, 50%. After 3 days, 100%. At any point, if something goes wrong, you flip the flag to off — no rollback, no deploy, no downtime. Feature flags are how mature teams ship fast. Not because they're fast — because they're safe.

What Are Feature Flags?

A feature flag is a server-side decision point that controls code execution. Simplified: your app asks "is this feature on for me?" Every time a user loads a page or performs an action, the server evaluates the flag and returns true or false. Feature A is on for 50% of users. Feature B is on for users in the EU. Feature C is only on for internal staff. That's it.

The magic is: once you ship code behind a flag, you can change behavior without deploying. You don't toggle the flag in code and redeploy — you change it in a dashboard and it's live in seconds. No CI pipeline. No downtime. That's why mature teams use flags on everything: new features, bug fixes, performance optimizations, even copy changes.

The Four Approaches — Trade-Offs

Approach 1: LaunchDarkly (Enterprise Standard)

LaunchDarkly is the default for teams with budgets. Pricing: $99/seat/month, minimum usually 3–5 seats, so $300–500/month. You get: web UI for toggling flags, SDK for every language, targeting rules (segment users by region, cohort, custom attributes), analytics, audit logs, and approval workflows for prod changes. If your company is Shopify-scale or a regulated industry (fintech, healthcare), LaunchDarkly is your play. You get a compliance audit trail and you don't have to build your own evaluator.

The catch: you're paying for features most SaaS teams don't need. You don't need a $300/month approval workflow. You don't need an audit trail of "who toggled the flag at 3pm". You need to ship fast and roll back if something breaks. LaunchDarkly is optimised for "don't break things" — which is good — but most startups optimise for "ship fast". So most startups overpay.

Approach 2: PostHog (Analytics-First)

PostHog is product analytics (session replay, heatmaps, funnels) *plus* feature flags bundled in. Free tier is generous: 1M events/month. If you're already logging analytics to PostHog, feature flags are just another feature. You create a flag in PostHog UI, your app checks the flag, PostHog logs the exposure (which users saw which variant), and you analyze the results using PostHog's built-in stats. No additional vendor. No additional cost if you're under 1M events/month.

The caveat: PostHog flags are good but not enterprise-grade. No approval workflows. Targeting rules are simpler than LaunchDarkly. The pricing ramps aggressively after 1M events — you could end up paying $1000+/month for analytics if you're a high-traffic SaaS. But for early-stage, PostHog flags are free and solid.

Approach 3: Statsig (Free at Scale)

Statsig is the upstart. Free tier is genuinely free: unlimited feature flags, up to 5M feature flag evaluations/month, built-in A/B testing, and targeting. Pricing only kicks in if you use their experimentation dashboard heavily. Statsig's pitch: same feature-flag power as LaunchDarkly, free if you run your own analytics. You manage flags in Statsig, your app evaluates flags locally (cached rules), and you log your own events to wherever you want (Supabase, Mixpanel, Amplitude).

The catch: you have to wire up your own event logging. Statsig doesn't auto-log exposures like PostHog does. But if you're already logging events to Supabase or another data warehouse, that's not a problem — you just do one extra query. Statsig has been around since 2020 and is solid. Most product teams sleep on it because LaunchDarkly has mindshare.

Approach 4: Roll Your Own (Supabase + Client Lib)

This is the Aidxn play. You control the code, the data lives in your database, and the setup takes a day. Here's the pattern: (1) create a features table with flag name, on/off state, and rollout percentage, (2) build a client library that queries Supabase, evaluates targeting rules (is this user in the rollout %), and returns true or false, (3) ship conditional rendering based on the result, (4) ship code with flags on — no long-lived branches. When you're confident, set rollout to 100% and the feature is on everywhere. No vendor lock-in. No monthly bill. Total control.

When to Pick Which

Pick LaunchDarkly if: you're enterprise-scale (500+ employees), you need audit trails and approval workflows, and $300–500/month is pocket change. You want someone else owning the infrastructure.

Pick PostHog if: you're already using PostHog for analytics. The flags integration is native and free if you're under their event quota. One vendor, one dashboard, one contract.

Pick Statsig if: you want enterprise-grade flags without the LaunchDarkly price tag, and you're comfortable owning event logging. Free tier is genuinely generous.

Roll your own if: you have Supabase (or Postgres), you're comfortable with SQL and a bit of TypeScript, and you want zero dependencies. Takes 1 day. Costs $0/month. This is the Aidxn play for early-stage SaaS.

DIY Feature Flags — The Supabase Pattern (Copy This)

Here's the pattern you can ship today. Simple. Scalable. Works for startups and mature products.

Step 1: Create the Features Table

CREATE TABLE features (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT UNIQUE NOT NULL,        -- 'checkout_v2', 'new_dashboard', etc.
  enabled BOOLEAN DEFAULT false,     -- global on/off
  rollout_percentage INTEGER DEFAULT 0,  -- 0-100, gradual rollout
  target_users TEXT[] DEFAULT '{}',  -- specific user IDs for early access
  created_at TIMESTAMP DEFAULT now(),
  updated_at TIMESTAMP DEFAULT now()
);

-- Add indexes for performance
CREATE INDEX idx_features_name ON features(name);

Step 2: Build the Client Library (isFeatureEnabled)

// lib/features.ts
import { supabase } from './supabase';
import { createHash } from 'crypto';

interface FeatureRule {
  name: string;
  enabled: boolean;
  rollout_percentage: number;
  target_users: string[];
}

// Cache features in memory for 60 seconds
let featureCache: Map = new Map();
let cacheExpiry = 0;

async function getFeatures(): Promise> {
  const now = Date.now();
  if (featureCache.size > 0 && now < cacheExpiry) {
    return featureCache;
  }

  const { data } = await supabase
    .from('features')
    .select('*');

  featureCache = new Map();
  if (data) {
    data.forEach((f) => {
      featureCache.set(f.name, {
        name: f.name,
        enabled: f.enabled,
        rollout_percentage: f.rollout_percentage,
        target_users: f.target_users || [],
      });
    });
  }

  cacheExpiry = now + 60000; // Cache for 60s
  return featureCache;
}

export async function isFeatureEnabled(
  featureName: string,
  userId: string
): Promise {
  const features = await getFeatures();
  const feature = features.get(featureName);

  if (!feature || !feature.enabled) {
    return false;
  }

  // Check if user is in explicit target list
  if (feature.target_users.includes(userId)) {
    return true;
  }

  // Check rollout percentage using consistent hashing
  // (same user always gets same result)
  const hash = createHash('sha256')
    .update(`${featureName}:${userId}`)
    .digest('hex');
  const hashInt = parseInt(hash.substring(0, 8), 16);
  const rolloutBucket = (hashInt % 100) + 1;

  return rolloutBucket <= feature.rollout_percentage;
}

Step 3: Use in React Components

// pages/checkout.tsx
import { isFeatureEnabled } from '@/lib/features';
import { useEffect, useState } from 'react';

export default function CheckoutPage({ userId }) {
  const [showV2, setShowV2] = useState(false);

  useEffect(() => {
    isFeatureEnabled('checkout_v2', userId).then(setShowV2);
  }, [userId]);

  return (
    
{showV2 ? ( ) : ( )}
); }

Step 4: Gradual Rollout in Production

-- Monday: ship code, feature flag OFF
UPDATE features SET enabled = true, rollout_percentage = 0 WHERE name = 'checkout_v2';

-- Tuesday morning: 1% of users
UPDATE features SET rollout_percentage = 1 WHERE name = 'checkout_v2';

-- Tuesday afternoon: 10% if no errors
UPDATE features SET rollout_percentage = 10 WHERE name = 'checkout_v2';

-- Wednesday: 50% if metrics look good
UPDATE features SET rollout_percentage = 50 WHERE name = 'checkout_v2';

-- Thursday: 100% (full rollout)
UPDATE features SET rollout_percentage = 100 WHERE name = 'checkout_v2';

-- If something breaks, instant rollback (no deploy):
UPDATE features SET rollout_percentage = 0 WHERE name = 'checkout_v2';

Step 5: Monitor Metrics During Rollout

-- Track conversion rates by feature variant
SELECT
  CASE
    WHEN (
      SELECT rollout_percentage
      FROM features
      WHERE name = 'checkout_v2'
    ) >= ((
      SELECT sum(('0x' || substr(md5(user_id || 'checkout_v2'), 1, 8))::bit(32)::int) % 100
    )) + 1 THEN 'v2'
    ELSE 'v1'
  END as variant,
  COUNT(*) as total_checkouts,
  COUNT(*) FILTER (WHERE completed_at IS NOT NULL) as completed,
  ROUND(
    100.0 * COUNT(*) FILTER (WHERE completed_at IS NOT NULL) / COUNT(*),
    2
  ) as conversion_rate
FROM checkouts
WHERE created_at > now() - interval '1 day'
GROUP BY variant;

That's it. One day of work. No vendor. No monthly bill. Code ships behind flags by default, and you control rollout with a single SQL UPDATE.

Production Rollout Strategies

The Cautious Rollout (recommended): 1% → 10% → 50% → 100%, staggered over days. Watch error rates, latency, conversion rates at each step. This is the safest approach. If something breaks at 1%, you catch it on day 1 when only 1% of users are affected.

The Staged Rollout by Cohort: Ship to internal staff first (target_users list), then to a specific customer cohort (high-value accounts or paid users), then to everyone. This lets you get feedback from people who care before rolling out to the whole internet.

The Time-of-Day Rollout: If you deploy at 3pm and something breaks, you're awake to fix it. Don't ship new features at 5pm on Friday. Ship at 9am on a Tuesday. Your team is online. Errors are visible in real time.

The Parallel Run: You have two codepaths (old checkout, new checkout). Run both in parallel for 24 hours. Log events from both. Compare error rates, latency, conversion rates. Once you're sure the new one is better, flip the flag and remove the old code from production.

Six FAQs

What if I don't ship every feature behind a flag?

You're shipping on long-lived branches. You merge to main, deploy, and hope nothing breaks. That's fine for small teams. Scale past 10 engineers and every merge is a potential breakage. Flags eliminate that risk.

Doesn't this bloat my codebase with if-statements?

Yes. Sometimes you clean it up, sometimes you don't. Once a feature is 100% rolled out and has been stable for a week, you can remove the flag and the conditional. But during development and rollout, yes, your code has more branches. That's the trade-off for safe deploys.

Can I use feature flags for A/B testing?

Yes. A flag that's on for 50% of users is an A/B test. But feature flags and A/B testing are different tools: flags are for shipping safely, A/B tests are for measuring impact. Use flags for safety, use A/B testing (with proper statistical analysis) for decisions. See our A/B testing guide for the full pattern.

How do I handle database migrations with feature flags?

This is the tricky part. If you're adding a new column and the feature depends on it, you can't toggle the feature on for users whose database row doesn't have the column yet. Solution: (1) deploy the migration first, backfill data, then (2) ship the feature code behind a flag. Never ship code that depends on schema that doesn't exist yet.

What if a user gets stuck with the old variant after rollout?

Your hashing function (consistent hash using user ID and feature name) ensures the same user always gets the same variant. Once rollout hits 100%, the check returns true for everyone. If they clear cookies or refresh, they still get the new version because the hash is deterministic. No sticky sessions needed.

Should I use Redis to cache flag evaluations?

Probably not at first. Supabase queries with a 60-second cache are fast enough for 95% of teams. Once you're at 10k+ requests/second and flag evaluation becomes a bottleneck (it won't), add a Redis layer in front. Start simple.

The Bottom Line

Feature flags are how teams ship fast without breaking things. LaunchDarkly is enterprise. PostHog is free if you're already using it. Statsig is the middle ground. Rolling your own in Supabase takes a day and costs nothing.

The Aidxn rule: use PostHog if you're already logging analytics there. Use Statsig if you want enterprise flags without LaunchDarkly's price. Only LaunchDarkly if you're enterprise-scale. For everyone else, roll your own.

Ship flags by default. Gradual rollout by default. Instant rollback by default. Once you work this way, you can't go back.

Need a feature flag strategy for your SaaS? Check out Aidxn Design's product engineering consulting to design your deployment pipeline, or read our full A/B testing guide to pair flags with experimentation.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.