Skip to main content

Feature Flags

Feature flags let you ship code dark, roll out to specific workspaces, or gate features by subscription plan — all without redeploying.

How Flags Work

Each flag has three resolution layers checked in order:
  1. Workspace override — a workspace-specific true/false stored in the workspace_overrides JSONB column. Overrides the global state for that workspace.
  2. Plan-based gating — (hook point, not yet implemented) check the workspace’s active plan against plan_ids.
  3. Global defaultenabled_globally boolean on the flag row.

Checking a Flag

import { checkFlag } from "@/lib/flags/actions";

// In a server action or server component
const isEnabled = await checkFlag("new-dashboard", workspaceId);
if (!isEnabled) redirect("/dashboard");
For client-side gating, use FeatureFlagProvider and the useFlag() hook (build these on top of getFlags() if needed).

Flag Schema

type FeatureFlag = {
  id: string;
  name: string;
  description: string | null;
  enabled_globally: boolean;
  plan_ids: string[];                      // Plans that get this feature
  workspace_overrides: Record<string, boolean>; // Per-workspace on/off
  created_at: string;
  updated_at: string;
};

Managing Flags

Super admins manage flags via the admin panel or directly via server actions:
import { getFlags, setFlag, setWorkspaceOverride } from "@/lib/flags/actions";

// Turn a flag on globally
await setFlag("new-dashboard", true);

// Force-enable for one workspace (overrides global)
await setWorkspaceOverride("new-dashboard", workspaceId, true);

// Remove the workspace override (falls back to global)
await setWorkspaceOverride("new-dashboard", workspaceId, null);

Creating a New Flag

Add a row to the feature_flags table:
INSERT INTO feature_flags (name, description, enabled_globally)
VALUES ('new-dashboard', 'Redesigned dashboard UI', false);
Then check it in code with checkFlag("new-dashboard", workspaceId).

Plan-Based Gating

The plan_ids column is reserved for subscription-based gating. To implement it, extend the checkFlag() function in lib/flags/actions.ts to look up the workspace’s active plan from your subscriptions table and return true if the plan ID is in flag.plan_ids.
The hook point is already in the code as a TODO comment in checkFlag(). Wire it up after you have subscription records in your database.

RLS

The feature_flags table uses RLS: all authenticated users can read flags, only super admins can write. This lets you safely call getFlags() from any authenticated server component without leaking sensitive state.