Feature Flags
Feature flags let you control which features are visible to which users, without deploying new code.
Defining Features
const config = defineAccess({
roles: ["admin", "editor", "viewer"] as const,
permissions: {
/* ... */
},
features: {
// Simple on/off
darkMode: { enabled: true },
maintenance: { enabled: false },
// Role-targeted
betaEditor: {
enabled: true,
allowedRoles: ["admin", "editor"],
},
// Plan-gated
advancedAnalytics: {
enabled: true,
allowedPlans: ["pro", "enterprise"],
},
// Environment-scoped
debugPanel: {
enabled: true,
allowedEnvironments: ["development", "staging"],
},
// Percentage rollout (deterministic, SSR-safe)
newDashboard: {
rolloutPercentage: 25,
},
// Dependencies
newCheckout: {
enabled: true,
dependencies: ["newDashboard"], // only if newDashboard is also enabled
},
// Boolean shorthand
simpleFlag: true,
},
plans: ["free", "pro", "enterprise"] as const,
environment: { name: "production" },
});Feature Definition Options
| Field | Type | Description |
|---|---|---|
enabled | boolean | Base enabled state |
allowedRoles | string[] | Enable only for these roles |
allowedPlans | string[] | Enable only for these plans |
allowedEnvironments | string[] | Enable only in these environments |
rolloutPercentage | number (0–100) | Percentage-based rollout |
dependencies | string[] | Required features that must also be enabled |
You can also use a simple boolean instead of a full definition object.
Evaluation Precedence
Features are evaluated in this order:
- Static —
enabled: falsestops evaluation immediately - Environment — Must match if
environmentsis specified - Role — User must have one of the specified roles
- Plan — User's plan must be in the specified plans
- Dependencies — All dependencies must be enabled (topological sort)
- Rollout — Deterministic hash of
userId + featureName(SSR-safe)
Using in Components
<Feature> Component
import { Feature } from "react-access-engine";
<Feature name="betaEditor" fallback={<ClassicEditor />}>
<BetaEditor />
</Feature>;<FeatureToggle> Render Prop
import { FeatureToggle } from "react-access-engine";
<FeatureToggle name="newDashboard">
{({ enabled, reason }) => (
<div>
<p>New Dashboard: {enabled ? "✅ Active" : "❌ Inactive"}</p>
<p>Reason: {reason}</p>
</div>
)}
</FeatureToggle>;useFeature Hook
import { useFeature } from "react-access-engine";
function Dashboard() {
const { enabled, reason } = useFeature("newDashboard");
if (!enabled) {
return <LegacyDashboard />;
}
return <NewDashboard />;
}Rollout Mechanism
Rollouts use a deterministic hash of userId + featureName:
- The same user always gets the same result (consistent experience)
- SSR-safe — no
Math.random(), works with server rendering - Rollout percentage is applied uniformly across users
features: {
newCheckout: {
rolloutPercentage: 10, // 10% of users
},
}Feature Dependencies
Features can depend on other features:
features: {
baseFeature: { enabled: true },
dependentFeature: {
enabled: true,
dependencies: ['baseFeature'], // only if baseFeature is on
},
}Dependencies are resolved via topological sort. Circular dependencies cause both features to be disabled.
Combining with <AccessGate>
<AccessGate feature="newDashboard" permission="dashboard:view" plan="pro">
<PremiumDashboard />
</AccessGate>This only renders if: the feature is enabled AND the user has the permission AND the user is on the pro plan (or higher).