⚡ Zero infra · just your database

Feature flags for Laravel,
nothing but your database.

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.

$ composer require chemaclass/laravel-feature-flags
if (FeatureFlag::isEnabled('new-dashboard')) {
    // ship it 🚀
}

How it works

Back end to front end, one clean path

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.

🗄️ Database

feature_flags table. Key, scope, environment, rules, variants — one row per override.

📦 Repository

Evaluates kill-switch → rules → rollout → boolean. Request-memoized + optional cache store.

🧭 Manager

Thin application service. Normalizes enum keys, threads the targeting context.

🎯 Your app

Reach it four ways — all resolve the same scope and context.

FacadeFeatureFlag::isEnabled()
Blade@feature … @endfeature
Middlewarefeature.enabled:key
Artisanflag:list / toggle / sync

Swap the repository (Redis, remote, in-memory) without touching a single call site — it's just a contract.

The front end

A Blade admin UI, published into your app

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.

yourapp.test/admin/feature-flags
Feature Flags · 4 keys · 6 rows
+ New flag
new-dashboard GLOBAL Q3 redesign rollout
new-dashboard team-42 early access
beta-search GLOBAL DEV 30% rollout gradual ramp
new-billing GLOBAL targeted: plan = pro
new-billing region-eu enabled_until 2026-12-01

Prefer your own layout? Disable the bundled routes and publish just the view.

What's in the box

Simple by default, a platform when you need it

Every feature is opt-in and back-compat. Start with a boolean; layer the rest on the same rows.

🔀

Per-scope overrides

Scope is any string — team, org, region, cohort, user. Scoped row beats global.

🎯

Attribute targeting

Rules on a context: plan = pro AND country in (DE,AT). 11 operators.

📈

Percentage rollout

Deterministic bucketing — a stable ~X% of scopes, never flips between checks.

🧪

A/B/n variants

Weighted variants with per-variant payloads for experiments and remote config.

🌍

Environments

Same key, different value per environment. Null applies everywhere.

🔗

Prerequisites & kill-switch

Require other flags; master off-switch to kill anything instantly.

⏱️

Time windows

enabled_from / enabled_until gating for scheduled launches.

Caching & real-time

Request memoization always on; optional store with instant cross-node invalidation.

📝

Audit log

Persist every toggle with actor + a per-flag history panel in the admin UI.

🧹

Stale-flag detection

flag:stale finds flags safe to delete — kill flag debt.

🏷️

Typed codegen

flag:generate writes a typed enum from your keys for IDE autocomplete.

🔧

GitOps & Pennant

flag:sync reconciles from a versioned file; optional Laravel Pennant bridge.

Show me the code

Reads like Laravel

From a one-line boolean to attribute targeting and variants — same facade, same rows.

Target by attributes

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

In Blade & the CLI

{{-- 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