Feature Flags in SaaS Boilerplates
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
| Boilerplate | Feature Flags | Provider |
|---|---|---|
| ShipFast | ❌ | — |
| Supastarter | ❌ | — |
| Makerkit | ✅ Optional | PostHog |
| T3 Stack | ❌ | — |
| Open SaaS | ✅ | PostHog |
Find boilerplates with analytics and feature flags on StarterPick.
Check out this boilerplate
View PostHog on StarterPick →