← Back to articles

How to Implement Feature Flags in Production (2026)

Feature flags let you deploy code without releasing features. Ship to production, then toggle features on for specific users. Here's how to implement them properly.

Why Feature Flags

  • Safe deployments — deploy code, enable feature gradually
  • A/B testing — show feature to 50% of users, measure impact
  • Kill switch — disable a broken feature instantly (no redeploy)
  • Beta programs — enable for specific users or organizations
  • Gradual rollouts — 1% → 10% → 50% → 100%

The Options

ToolBest ForPricing
PostHogFeature flags + analyticsFree (1M events/mo)
LaunchDarklyEnterprise-grade flags$10/seat/mo
Vercel FlagsVercel/Next.js appsFree (with Vercel)
KindeAuth + flags combinedFree (10.5K MAU)
CustomSimple needsFree

Option 1: PostHog (Recommended)

Feature flags + product analytics in one tool.

npm install posthog-js

Setup

// lib/posthog.ts
import posthog from 'posthog-js'

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
  api_host: 'https://us.i.posthog.com',
})

export default posthog

Check Feature Flag

// Client-side
import posthog from '@/lib/posthog'

function Dashboard() {
  const showNewUI = posthog.isFeatureEnabled('new-dashboard-ui')

  if (showNewUI) {
    return <NewDashboard />
  }
  return <OldDashboard />
}

Server-Side (Next.js)

import { PostHog } from 'posthog-node'

const posthog = new PostHog(process.env.POSTHOG_API_KEY!)

export default async function DashboardPage() {
  const flags = await posthog.getAllFlags('user-123')

  if (flags['new-dashboard-ui']) {
    return <NewDashboard />
  }
  return <OldDashboard />
}

Create Flag in PostHog Dashboard

  1. Go to Feature Flags → New Flag
  2. Set key: new-dashboard-ui
  3. Set rollout: 10% of users (or specific users/groups)
  4. Save and deploy

Option 2: Custom Implementation (Simple)

For simple boolean flags without a third-party service:

// lib/flags.ts
type FeatureFlags = {
  newDashboard: boolean
  aiAssistant: boolean
  darkMode: boolean
}

const flags: Record<string, FeatureFlags> = {
  production: {
    newDashboard: false,
    aiAssistant: true,
    darkMode: true,
  },
  staging: {
    newDashboard: true,
    aiAssistant: true,
    darkMode: true,
  },
}

export function getFlags(): FeatureFlags {
  const env = process.env.NODE_ENV === 'production' ? 'production' : 'staging'
  return flags[env]
}

export function isEnabled(flag: keyof FeatureFlags): boolean {
  return getFlags()[flag]
}

Usage

import { isEnabled } from '@/lib/flags'

if (isEnabled('newDashboard')) {
  return <NewDashboard />
}

Database-Backed Flags (More Flexible)

// Store flags in your database
const flags = pgTable('feature_flags', {
  key: text('key').primaryKey(),
  enabled: boolean('enabled').default(false),
  rolloutPercentage: integer('rollout_percentage').default(0),
  allowedUserIds: text('allowed_user_ids').array(),
})

export async function isEnabled(flagKey: string, userId: string): Promise<boolean> {
  const flag = await db.query.flags.findFirst({
    where: eq(flags.key, flagKey),
  })

  if (!flag || !flag.enabled) return false

  // Check if user is in allowlist
  if (flag.allowedUserIds?.includes(userId)) return true

  // Check rollout percentage
  const hash = simpleHash(userId + flagKey)
  return (hash % 100) < flag.rolloutPercentage
}

Option 3: Vercel Flags (Next.js)

// flags.ts
import { flag } from '@vercel/flags/next'

export const newDashboard = flag({
  key: 'new-dashboard',
  decide: () => false, // Default value
})

// In a Server Component
import { newDashboard } from '@/flags'

export default async function Page() {
  const showNew = await newDashboard()
  return showNew ? <NewDashboard /> : <OldDashboard />
}

Best Practices

1. Name Flags Clearly

✅ new-checkout-flow
✅ ai-assistant-v2
✅ premium-analytics
❌ flag1
❌ test
❌ johns-feature

2. Clean Up Old Flags

Remove flags after full rollout. Stale flags create technical debt.

Flag created: Jan 1
Rolled out to 100%: Jan 15
Flag removed from code: Jan 22 ← Don't skip this!

3. Use Server-Side Evaluation

Evaluate flags on the server when possible. Client-side evaluation can flicker (show old UI, then switch to new).

4. Have a Kill Switch

Every major feature should have a flag. If something breaks in production, disable the flag instantly — no rollback needed.

5. Log Flag Evaluations

Track which users see which variants. Essential for debugging and A/B test analysis.

Gradual Rollout Strategy

Day 1:  Enable for internal team (QA)
Day 3:  Enable for 5% of users
Day 5:  Check metrics — errors, performance, user feedback
Day 7:  Enable for 25% of users
Day 10: Enable for 50% of users
Day 14: Enable for 100% of users
Day 21: Remove flag from code

FAQ

Do feature flags slow down my app?

With server-side evaluation: negligible. With client-side: depends on SDK initialization time (~50-100ms for PostHog). Cache flag values to minimize impact.

How many flags should I have?

As few as possible. 10-20 active flags is manageable. 100+ becomes technical debt. Clean up after rollout.

Should every feature use a flag?

Major features and risky changes: yes. Small bug fixes and copy changes: no. Use judgment.

PostHog vs LaunchDarkly?

PostHog: free tier, includes analytics, good for startups. LaunchDarkly: more flag features, better for large enterprises. Most teams: PostHog.

Bottom Line

Feature flags are essential for safe production deployments. Start with PostHog (free, includes analytics) or build a simple custom solution. The key patterns: gradual rollout, kill switches, and cleaning up old flags. Every major feature should ship behind a flag.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.