Simple on/off toggles when that's all you want. Attribute targeting, percentage rollouts, A/B variants, per-environment values, and a Blade admin UI when you need more.
if (FeatureFlag::isEnabled('new-dashboard')) { // ship it 🚀 }
How it works
Your DB is the source of truth. A cached repository reads it, a manager wraps it, and you reach flags through a facade, Blade, middleware, or Artisan — pick whatever fits.
feature_flags table. Key, scope, environment, rules, variants — one row per override.
Evaluates kill-switch → rules → rollout → boolean. Request-memoized + optional cache store.
→Thin application service. Normalizes enum keys, threads the targeting context.
→Reach it four ways — all resolve the same scope and context.
Swap the repository (Redis, remote, in-memory) without touching a single call site — it's just a contract.
The front end
Flags grouped by key, scope overrides nested underneath, real sliding toggles, inline editing, dev markers, dark mode. No build step, no SPA — just a route.
Prefer your own layout? Disable the bundled routes and publish just the view.
What's in the box
Every feature is opt-in and back-compat. Start with a boolean; layer the rest on the same rows.
Scope is any string — team, org, region, cohort, user. Scoped row beats global.
Rules on a context: plan = pro AND country in (DE,AT). 11 operators.
Deterministic bucketing — a stable ~X% of scopes, never flips between checks.
Weighted variants with per-variant payloads for experiments and remote config.
Same key, different value per environment. Null applies everywhere.
Require other flags; master off-switch to kill anything instantly.
enabled_from / enabled_until gating for scheduled launches.
Request memoization always on; optional store with instant cross-node invalidation.
Persist every toggle with actor + a per-flag history panel in the admin UI.
flag:stale finds flags safe to delete — kill flag debt.
flag:generate writes a typed enum from your keys for IDE autocomplete.
flag:sync reconciles from a versioned file; optional Laravel Pennant bridge.
Show me the code
From a one-line boolean to attribute targeting and variants — same facade, same rows.
FeatureFlag::updateOrCreate( ['key' => 'new-billing', 'scope_id' => null], [ 'value' => false, 'rules' => [ ['when' => [['attr'=>'plan','op'=>'eq','value'=>'pro']], 'then' => true], ], ], ); FeatureFlag::isEnabled('new-billing', $userId, ['plan'=>'pro']); // → true
{{-- resources/views/home.blade.php --}} @feature('new-dashboard') <x-new-dashboard /> @else <x-legacy-dashboard /> @endfeature # create, roll out, ship $ php artisan flag:create new-dashboard --value=1 $ php artisan flag:toggle beta-search $ php artisan flag:stale --days=30 $ php artisan flag:sync --prune