Loading...
Loading...
> Control feature visibility with toggles, percentage rollouts, and user targeting for safe deployments.
Feature flags allow you to control feature visibility without deploying new code. Use them for gradual rollouts, A/B testing, user targeting, and kill switches.
Feature flag model in Prisma schema
1// prisma/schema.prisma2model FeatureFlag {3 id String @id @default(cuid())4 key String @unique // "new-dashboard", "beta-api"5 name String // Human-readable name6 description String?78 enabled Boolean @default(false)910 // Percentage rollout (0-100)11 percentage Int @default(100)1213 // User targeting14 userIds String[] // Specific user IDs1516 // Plan targeting17 plans String[] // ["pro", "enterprise"]1819 // Environment20 environment String @default("production")2122 createdAt DateTime @default(now())23 updatedAt DateTime @updatedAt2425 @@index([key, environment])26}
Core service for checking feature flags
1// src/lib/feature-flags.ts2import { prisma } from "@/lib/db";3import crypto from "crypto";45interface FlagContext {6 userId?: string;7 plan?: string;8 environment?: string;9}1011export async function isFeatureEnabled(12 key: string,13 context: FlagContext = {}14): Promise<boolean> {15 const environment = context.environment || process.env.NODE_ENV;1617 const flag = await prisma.featureFlag.findFirst({18 where: { key, environment },19 });2021 if (!flag) {22 return false; // Default to disabled for unknown flags23 }2425 if (!flag.enabled) {26 return false;27 }2829 // Check user targeting30 if (flag.userIds.length > 0 && context.userId) {31 if (flag.userIds.includes(context.userId)) {32 return true;33 }34 }3536 // Check plan targeting37 if (flag.plans.length > 0 && context.plan) {38 if (!flag.plans.includes(context.plan)) {39 return false;40 }41 }4243 // Check percentage rollout44 if (flag.percentage < 100 && context.userId) {45 const hash = crypto46 .createHash("md5")47 .update(`${key}:${context.userId}`)48 .digest("hex");49 const value = parseInt(hash.slice(0, 8), 16) % 100;5051 if (value >= flag.percentage) {52 return false;53 }54 }5556 return true;57}5859// Get all flags for a user (useful for client-side)60export async function getFeatureFlags(context: FlagContext = {}) {61 const environment = context.environment || process.env.NODE_ENV;6263 const flags = await prisma.featureFlag.findMany({64 where: { environment },65 });6667 const result: Record<string, boolean> = {};6869 for (const flag of flags) {70 result[flag.key] = await isFeatureEnabled(flag.key, context);71 }7273 return result;74}
Check feature flags in API routes and server components
1// In API routes2import { auth } from "@/lib/auth";3import { isFeatureEnabled } from "@/lib/feature-flags";45export async function GET(req: Request) {6 const session = await auth();78 const hasNewDashboard = await isFeatureEnabled("new-dashboard", {9 userId: session?.user?.id,10 plan: session?.user?.plan,11 });1213 if (!hasNewDashboard) {14 return Response.json(15 { error: "Feature not available" },16 { status: 403 }17 );18 }1920 // Return new dashboard data...21}2223// In Server Components24import { isFeatureEnabled } from "@/lib/feature-flags";25import { auth } from "@/lib/auth";2627export default async function DashboardPage() {28 const session = await auth();2930 const showBetaFeatures = await isFeatureEnabled("beta-features", {31 userId: session?.user?.id,32 plan: session?.user?.plan,33 });3435 return (36 <div>37 <h1>Dashboard</h1>38 {showBetaFeatures && (39 <div className="bg-primary/10 p-4 border border-border">40 <h2>Beta Features</h2>41 {/* Beta content */}42 </div>43 )}44 </div>45 );46}
Use feature flags in client components with a React hook
1// src/hooks/use-feature-flags.ts2"use client";34import { createContext, useContext, useEffect, useState } from "react";56interface FeatureFlagsContextType {7 flags: Record<string, boolean>;8 isEnabled: (key: string) => boolean;9 loading: boolean;10}1112const FeatureFlagsContext = createContext<FeatureFlagsContextType>({13 flags: {},14 isEnabled: () => false,15 loading: true,16});1718export function FeatureFlagsProvider({19 children,20}: {21 children: React.ReactNode;22}) {23 const [flags, setFlags] = useState<Record<string, boolean>>({});24 const [loading, setLoading] = useState(true);2526 useEffect(() => {27 fetch("/api/v1/feature-flags")28 .then((res) => res.json())29 .then((data) => {30 setFlags(data.flags);31 setLoading(false);32 });33 }, []);3435 const isEnabled = (key: string) => flags[key] ?? false;3637 return (38 <FeatureFlagsContext.Provider value={{ flags, isEnabled, loading }}>39 {children}40 </FeatureFlagsContext.Provider>41 );42}4344export function useFeatureFlags() {45 return useContext(FeatureFlagsContext);46}4748export function useFeatureFlag(key: string) {49 const { isEnabled, loading } = useFeatureFlags();50 return { enabled: isEnabled(key), loading };51}5253// Usage in components54"use client";5556import { useFeatureFlag } from "@/hooks/use-feature-flags";5758export function NewFeature() {59 const { enabled, loading } = useFeatureFlag("new-feature");6061 if (loading) return null;62 if (!enabled) return null;6364 return (65 <div className="bg-success/10 p-4 border border-border">66 <h3>New Feature Available!</h3>67 {/* Feature content */}68 </div>69 );70}
API routes for managing feature flags
1// POST /api/admin/feature-flags2export async function POST(req: Request) {3 const session = await auth();4 if (session?.user?.role !== "admin") {5 return Response.json({ error: "Forbidden" }, { status: 403 });6 }78 const { key, name, description, enabled, percentage, userIds, plans } = await req.json();910 const flag = await prisma.featureFlag.create({11 data: {12 key,13 name,14 description,15 enabled: enabled ?? false,16 percentage: percentage ?? 100,17 userIds: userIds ?? [],18 plans: plans ?? [],19 },20 });2122 return Response.json({ flag });23}2425// PATCH /api/admin/feature-flags/:id26export async function PATCH(27 req: Request,28 { params }: { params: { id: string } }29) {30 const session = await auth();31 if (session?.user?.role !== "admin") {32 return Response.json({ error: "Forbidden" }, { status: 403 });33 }3435 const updates = await req.json();3637 const flag = await prisma.featureFlag.update({38 where: { id: params.id },39 data: updates,40 });4142 return Response.json({ flag });43}4445// Quick toggle endpoint46// POST /api/admin/feature-flags/:key/toggle47export async function POST(48 req: Request,49 { params }: { params: { key: string } }50) {51 const flag = await prisma.featureFlag.findUnique({52 where: { key: params.key },53 });5455 if (!flag) {56 return Response.json({ error: "Flag not found" }, { status: 404 });57 }5859 const updated = await prisma.featureFlag.update({60 where: { key: params.key },61 data: { enabled: !flag.enabled },62 });6364 return Response.json({ flag: updated });65}
new-checkout-flow not flag1