Back to blog
saasdevelopmentfeature-flagsdevopsarchitecture

Feature Flags in Your SaaS: Ship Faster Without the Risk

By SaaS Masters8 maart 20267 min read
Feature Flags in Your SaaS: Ship Faster Without the Risk

Feature flags (also known as feature toggles) are one of the most powerful tools in a modern SaaS team's arsenal. They allow you to safely roll out new features, experiment with different user groups, and quickly roll back if something goes wrong — without a new deployment.

In this article, we'll take a deep dive into feature flags: how they work, when to use them, and how to implement them in a production SaaS environment.

What are feature flags?

A feature flag is simply a condition in your code that determines whether a piece of functionality is active:

if (featureFlags.isEnabled('new-dashboard', { userId: user.id })) {
  return <NewDashboard />;
} else {
  return <LegacyDashboard />;
}

Instead of deploying code and immediately making it live for everyone, you wrap new functionality in a flag. You can then toggle that flag on and off via a configuration panel, without changing code or redeploying.

Why feature flags are essential for SaaS

1. Enabling trunk-based development

Without feature flags, teams often work with long-lived feature branches. This leads to merge conflicts, integration issues, and slow release cycles. With feature flags, you can push unfinished code to main — the flag keeps it hidden from users.

2. Progressive rollout

Instead of activating a feature for all your 10,000 users at once, you can scale up gradually:

  • 1% — internal testing
  • 5% — beta users
  • 25% — early adopters
  • 100% — full rollout

If problems arise at any point, you turn off the flag and it's resolved.

3. Built-in A/B testing

Feature flags are the foundation for A/B testing. You can offer two variants of a feature to different user groups and measure which performs better.

4. Plan-based features

For SaaS with multiple pricing tiers, feature flags are ideal:

const PLAN_FEATURES = {
  starter: ['basic-analytics', 'email-support'],
  professional: ['basic-analytics', 'email-support', 'advanced-reports', 'api-access'],
  enterprise: ['basic-analytics', 'email-support', 'advanced-reports', 'api-access', 'sso', 'audit-log', 'custom-branding'],
};

Building feature flags yourself: a practical implementation

Let's build a lightweight feature flag system that you can use in your own SaaS.

Database schema

CREATE TABLE feature_flags (
  id TEXT PRIMARY KEY,
  name TEXT UNIQUE NOT NULL,
  description TEXT,
  enabled BOOLEAN DEFAULT false,
  rollout_percentage INT DEFAULT 0,
  target_plans TEXT[] DEFAULT '{}',
  target_user_ids TEXT[] DEFAULT '{}',
  created_at TIMESTAMP DEFAULT now(),
  updated_at TIMESTAMP DEFAULT now()
);

CREATE TABLE feature_flag_overrides (
  id TEXT PRIMARY KEY,
  flag_name TEXT REFERENCES feature_flags(name),
  tenant_id TEXT NOT NULL,
  enabled BOOLEAN NOT NULL,
  created_at TIMESTAMP DEFAULT now(),
  UNIQUE(flag_name, tenant_id)
);

Server-side evaluation

interface FlagContext {
  userId: string;
  tenantId: string;
  plan: string;
}

class FeatureFlagService {
  private cache: Map<string, FeatureFlag> = new Map();

  async isEnabled(flagName: string, context: FlagContext): Promise<boolean> {
    const flag = await this.getFlag(flagName);
    if (!flag) return false;

    // Check tenant-level override first
    const override = await this.getOverride(flagName, context.tenantId);
    if (override !== null) return override;

    // Global kill switch
    if (!flag.enabled) return false;

    // Plan-based targeting
    if (flag.targetPlans.length > 0 && !flag.targetPlans.includes(context.plan)) {
      return false;
    }

    // Specific user targeting (beta testers)
    if (flag.targetUserIds.includes(context.userId)) return true;

    // Percentage-based rollout (deterministic hash)
    if (flag.rolloutPercentage < 100) {
      const hash = this.hashUserId(context.userId, flagName);
      return hash < flag.rolloutPercentage;
    }

    return true;
  }

  private hashUserId(userId: string, flagName: string): number {
    const str = `${userId}:${flagName}`;
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = ((hash << 5) - hash) + str.charCodeAt(i);
      hash = hash & hash;
    }
    return Math.abs(hash) % 100;
  }
}

React hook for the frontend

import { createContext, useContext } from 'react';

const FeatureFlagContext = createContext<Record<string, boolean>>({});

export function useFeatureFlag(flagName: string): boolean {
  const flags = useContext(FeatureFlagContext);
  return flags[flagName] ?? false;
}

// Usage in components:
function PricingPage() {
  const hasNewPricing = useFeatureFlag('new-pricing-page');

  if (hasNewPricing) {
    return <NewPricingPage />;
  }
  return <CurrentPricingPage />;
}

Admin dashboard endpoint

// API route: PATCH /api/admin/feature-flags/:name
export async function updateFlag(req: Request) {
  const { name } = req.params;
  const { enabled, rolloutPercentage, targetPlans } = req.body;

  await db.featureFlags.update({
    where: { name },
    data: {
      enabled,
      rolloutPercentage,
      targetPlans,
      updatedAt: new Date(),
    },
  });

  // Invalidate cache across all instances
  await redis.publish('feature-flag-update', JSON.stringify({ name }));

  // Log the change for audit trail
  await db.auditLog.create({
    data: {
      action: 'feature_flag_updated',
      resource: name,
      changes: { enabled, rolloutPercentage, targetPlans },
      userId: req.user.id,
    },
  });

  return Response.json({ success: true });
}

Build vs buy: when to choose an external service

Build it yourself when:

  • You need a simple setup (< 20 flags)
  • You want full control over the data
  • You don't want extra costs for an external service
  • Your flags are primarily plan-based

Use a service when:

  • You need complex targeting rules
  • You want A/B testing with statistical significance
  • Your team is growing and you need governance
  • You want real-time analytics per flag

Popular options:

  • LaunchDarkly — the industry standard, powerful but pricey
  • Unleash — open-source, self-hostable
  • PostHog — combines feature flags with product analytics
  • Flagsmith — open-source with a good free tier

Best practices for feature flags in production

1. Naming and organization

Use a consistent naming convention:

release/new-dashboard        → temporary release flag
experiment/pricing-v2        → A/B test
ops/maintenance-mode         → operational toggle
permission/advanced-reports  → plan-based feature

2. Clean up after use

Feature flags that are permanently on are technical debt. Plan a cleanup cycle:

// Add an expiration date to your flags
interface FeatureFlag {
  name: string;
  expiresAt?: Date; // Alert when this date has passed
  owner: string;    // Who is responsible?
}

// In your CI/CD pipeline: warn about expired flags
const expiredFlags = await db.featureFlags.findMany({
  where: {
    expiresAt: { lt: new Date() },
    enabled: true,
  },
});

if (expiredFlags.length > 0) {
  console.warn(`⚠️ ${expiredFlags.length} feature flags have expired and need cleanup`);
}

3. Monitoring and alerting

Connect your feature flags to your monitoring:

  • Track error rates per flag variant
  • Monitor performance impact of new features
  • Set up automatic rollback on elevated error rates

4. Document your flags

Maintain an overview of active flags, their purpose, and owner. This prevents flags from becoming orphaned in your codebase.

5. Test both paths

Make sure your CI/CD pipeline tests both variants — with the flag on and off. Otherwise, you'll only discover bugs when disabling a flag.

A real-world scenario

Imagine: your SaaS has built a new AI-powered search system. Instead of a big-bang release:

  1. Week 1: Deploy with flag off. Code is in production, but nobody sees it.
  2. Week 2: Enable the flag for your internal team. Test in the real production environment.
  3. Week 3: Activate for 5% of your users. Monitor latency and relevance.
  4. Week 4: Scale up to 25%. Collect feedback.
  5. Week 5: 100% rollout. Remove the old search code and the feature flag.

If at any point latency spikes or users complain, you roll back the flag. No hotfix, no panic, no new deployment.

Conclusion

Feature flags aren't just a development tool — they're a business tool. They give you the control to launch features at the right time, for the right users, in the right way.

Start small: implement a simple flag system for your next feature. You'll find that it transforms your entire release process. Ship faster, less risk, more control.

Want help implementing feature flags in your SaaS? Get in touch for a no-obligation conversation about your architecture.