webrek/laravel-feature-flags

Feature flags for Laravel with percentage rollouts, rule-based targeting and A/B variants.

Maintainers

Package info

github.com/webrek/laravel-feature-flags

pkg:composer/webrek/laravel-feature-flags

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-06-08 01:36 UTC

This package is auto-updated.

Last update: 2026-06-08 02:53:02 UTC


README

Latest Version on Packagist Total Downloads Tests PHP Version License

Feature flags for Laravel with percentage rollouts, rule-based targeting and A/B variants — flip features at runtime without a deploy.

Quickstart

composer require webrek/laravel-feature-flags
php artisan vendor:publish --tag=feature-flags-migrations
php artisan migrate
use Webrek\FeatureFlags\Facades\Features;

// Define a feature rolled out to 25% of users:
Features::create('new-checkout', rollout: 25);

// Check it (defaults to the authenticated user):
if (Features::active('new-checkout')) {
    // ...
}

// Or for a specific scope:
Features::for($user)->active('new-checkout');
@feature('new-checkout')
    <x-checkout.v2 />
@endfeature

Why not roll your own boolean column

A boolean column on a settings table answers one question: is this on for everyone? Real feature work needs more:

  • Gradual rollout. Ship to 5% of users, watch your metrics, raise it to 25%, then 100% — and a user who was in the 5% stays in as you climb, because bucketing is deterministic, not random per request.
  • Targeting. "Enterprise plans only", "users in MX and US", "accounts older than 30 days" — expressed as constraints, not branches scattered through code.
  • A/B variants. Assign each user a stable variant (blue vs green) and measure which converts.
  • Runtime control. Flip a flag from the database or an artisan command without a deploy, and kill a misbehaving feature instantly.

This package does all of that, and unlike Laravel Pennant it stores rollouts, constraints and variants as data you can manage — not just closures in code.

Defining features

With the database store (the default), define and manage at runtime:

Features::create(
    'enterprise-export',
    active: true,
    constraints: [
        ['attribute' => 'plan', 'operator' => 'in', 'value' => ['pro', 'enterprise']],
    ],
);

Features::create('button-color', variants: [
    ['name' => 'blue',  'weight' => 50],
    ['name' => 'green', 'weight' => 50],
]);

Features::activate('new-checkout');
Features::deactivate('new-checkout');
Features::rollout('new-checkout', 50);
Features::forget('old-flag');

Or declare them in code with the array store (great for tests or simple apps) — set FEATURE_FLAGS_STORE=array and fill config/feature-flags.php:

'features' => [
    'new-checkout' => ['active' => true, 'rollout' => 25],
    'button-color' => ['active' => true, 'variants' => [
        ['name' => 'blue', 'weight' => 50],
        ['name' => 'green', 'weight' => 50],
    ]],
],

Checking features

Features::active('new-checkout');             // default scope (auth user)
Features::active('new-checkout', $user);       // explicit scope
Features::inactive('new-checkout', $team);
Features::variant('button-color', $user);      // 'blue' | 'green' | null
Features::for($user)->active('new-checkout');  // fluent

feature('new-checkout');                       // helper, returns bool
feature();                                      // helper, returns the manager

A feature resolves to active only when every gate passes: the master switch is on, the scope matches all constraints, it falls within the rollout percentage, and (for a variant feature) a variant is assigned.

Scopes

Pass anything as a scope:

  • null (or omit) — the authenticated user, falling back to a global scope.
  • An Eloquent model — bucketed by class + key; its attributes feed targeting.
  • Anything implementing FeatureScope — you control the identifier and the attributes exposed to constraints.
  • A string or int — used directly as the bucketing identity.
use Webrek\FeatureFlags\Contracts\FeatureScope;

class Team extends Model implements FeatureScope
{
    public function featureScopeIdentifier(): string
    {
        return 'team:' . $this->id;
    }

    public function featureScopeAttributes(): array
    {
        return ['plan' => $this->plan, 'seats' => $this->seats];
    }
}

Targeting constraints

Each constraint is ['attribute' => ..., 'operator' => ..., 'value' => ...] and all must pass. Supported operators:

= · != · in · not_in · > · >= · < · <= · contains

constraints: [
    ['attribute' => 'plan', 'operator' => 'in', 'value' => ['pro', 'enterprise']],
    ['attribute' => 'seats', 'operator' => '>=', 'value' => 10],
]

Blade & middleware

@feature('new-dashboard')
    <x-dashboard.v2 />
@endfeature

@unlessfeature('new-dashboard')
    <x-dashboard.v1 />
@endfeature
Route::get('/beta', BetaController::class)->middleware('feature:new-dashboard');
// 404 unless the feature is active for the current user

Artisan

php artisan feature:list
php artisan feature:activate new-checkout
php artisan feature:deactivate new-checkout
php artisan feature:rollout new-checkout 50

Dashboard

A built-in web UI to toggle features, adjust rollout, and create or delete flags at runtime — no deploy, no database client. It is server-rendered (no JS build, no CDN) and lives at /feature-flags by default.

// config/feature-flags.php
'dashboard' => [
    'enabled' => env('FEATURE_FLAGS_DASHBOARD', true),
    'path' => 'feature-flags',
    'middleware' => ['web'],
],

The dashboard controls your flags, so protect it. Add auth/authorization middleware (e.g. ['web', 'auth', 'can:manage-features']) and, in production, restrict who can reach it. It manages the active store, so use the database store. Publish the views to customise them:

php artisan vendor:publish --tag=feature-flags-views

Requirements

Component Version
PHP 8.2+
Laravel 12.x

Testing

composer install
composer test

Contributing

See CONTRIBUTING.md.

Security

Please review the security policy before reporting a vulnerability.

License

The MIT License (MIT). See LICENSE.