PHPackages                             mugiwara/laravel-feature-flags - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. mugiwara/laravel-feature-flags

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

mugiwara/laravel-feature-flags
==============================

A flexible feature flag package for Laravel with database, config, multi-tenancy, and percentage rollout support.

v1.0.0(3mo ago)31MITPHPPHP ^8.1

Since Feb 14Pushed 3mo agoCompare

[ Source](https://github.com/sohaibhbill/laravel-feature-flags)[ Packagist](https://packagist.org/packages/mugiwara/laravel-feature-flags)[ RSS](/packages/mugiwara-laravel-feature-flags/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (6)Versions (2)Used By (0)

Laravel Feature Flags
=====================

[](#laravel-feature-flags)

[![Latest Version on Packagist](https://camo.githubusercontent.com/fb5122dec2a31fea18565eebdc1be6ca7aa0fab9ad313ea30ed2f58ee35a4016/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d756769776172612f6c61726176656c2d666561747572652d666c6167732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mugiwara/laravel-feature-flags)[![License: MIT](https://camo.githubusercontent.com/458425f8985b0b0c8a736cffe75e05a098e3d77906acddbcad2bfc54492a4e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/c3d372b55ac2d4fcf386a178e11d9788310097b35f3893cf3daae574b6b4cd3e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e312d626c75652e7376673f7374796c653d666c61742d737175617265)](https://php.net)[![Laravel Version](https://camo.githubusercontent.com/8633a273624f91777568896aa34dea64c23830ccb4ec2b0879da059472f383a1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c61726176656c2d31302e7825323025374325323031312e7825323025374325323031322e782d7265642e7376673f7374796c653d666c61742d737175617265)](https://laravel.com)

A powerful, zero-dependency feature flag package for Laravel. Control feature rollouts across every layer of your application — routes, Blade views, Eloquent models, service classes, and relationships — with multi-tenancy, user segments, and percentage-based rollouts built in.

Features
--------

[](#features)

- **Two storage drivers** — config file (zero setup) or database (full runtime control)
- **Route middleware** — gate routes with 4 response types (404, 403, JSON, redirect)
- **Blade directives** — `@feature`, `@elsefeature`, `@unlessfeature`, `@featureany`
- **Model protection** — hide entire Eloquent models via global scopes
- **Service method gating** — guard business logic with exceptions or silent fallbacks
- **Relationship scoping** — gate `hasMany`, `hasOne`, `belongsTo`, `belongsToMany`
- **User segment targeting** — restrict features to specific user groups
- **Percentage rollout** — gradual rollout with consistent hashing (CRC32)
- **Multi-tenancy** — tenant-scoped flags with global fallback inheritance
- **Artisan commands** — `feature:list`, `feature:create`, `feature:enable`, `feature:disable`
- **Events** — `FeatureEnabled` and `FeatureDisabled` dispatched on every toggle
- **Caching** — configurable TTL with automatic invalidation via model observer
- **Custom drivers** — implement the `FeatureDriver` interface for Redis, etc.
- **Custom resolvers** — override all checks with your own logic

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10.x, 11.x, or 12.x

---

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Configuration](#configuration)
- [Quick Start](#quick-start)
- [Drivers](#drivers)
    - [Config Driver](#config-driver)
    - [Database Driver](#database-driver)
    - [Custom Drivers](#custom-drivers)
- [Usage](#usage)
    - [Facade](#facade)
    - [Dependency Injection](#dependency-injection)
    - [Route Middleware](#route-middleware)
    - [Blade Directives](#blade-directives)
    - [Model Protection](#model-protection)
    - [Service Method Gating](#service-method-gating)
    - [Relationship Scoping](#relationship-scoping)
- [User Segments](#user-segments)
- [Percentage Rollouts](#percentage-rollouts)
- [Multi-Tenancy](#multi-tenancy)
- [Artisan Commands](#artisan-commands)
- [Events](#events)
- [Exception Handling](#exception-handling)
- [Custom Resolvers](#custom-resolvers)
- [FeatureFlag Model](#featureflag-model)
- [Architecture](#architecture)
- [Testing](#testing)
- [License](#license)

---

Installation
------------

[](#installation)

```
composer require mugiwara/laravel-feature-flags
```

The package uses Laravel's auto-discovery — no manual provider registration needed.

**Publish the config file:**

```
php artisan vendor:publish --tag=feature-flags-config
```

**If using the database driver**, also publish and run the migration:

```
php artisan vendor:publish --tag=feature-flags-migrations
php artisan migrate
```

> If you have disabled auto-discovery, register the provider and facade manually:
>
> ```
> // config/app.php
> 'providers' => [
>     MugiWara\FeatureFlags\FeatureFlagsServiceProvider::class,
> ],
>
> 'aliases' => [
>     'Feature' => MugiWara\FeatureFlags\Facades\Feature::class,
> ],
> ```

---

Configuration
-------------

[](#configuration)

The published config file is at `config/features.php`:

```
return [
    /*
    |--------------------------------------------------------------------------
    | Feature Flags
    |--------------------------------------------------------------------------
    |
    | Define your feature flags here. Each flag can be toggled via env vars.
    | These are used by the config driver. The database driver reads from
    | the feature_flags table instead.
    |
    */
    'flags' => [
        'beta_dashboard'     => env('FEATURE_BETA_DASHBOARD', false),
        'advanced_reporting' => env('FEATURE_ADVANCED_REPORTING', false),
        'premium_content'    => env('FEATURE_PREMIUM_CONTENT', false),
        'api_v2'             => env('FEATURE_API_V2', false),
    ],

    /*
    |--------------------------------------------------------------------------
    | Driver
    |--------------------------------------------------------------------------
    |
    | Supported: "config", "database"
    |
    */
    'driver' => env('FEATURE_DRIVER', 'config'),

    /*
    |--------------------------------------------------------------------------
    | Cache (database driver only)
    |--------------------------------------------------------------------------
    */
    'cache' => [
        'enabled' => env('FEATURE_CACHE_ENABLED', true),
        'ttl'     => env('FEATURE_CACHE_TTL', 3600), // seconds
        'prefix'  => 'feature_flag:',
    ],
];
```

**Environment variables:**

```
FEATURE_DRIVER=database
FEATURE_CACHE_ENABLED=true
FEATURE_CACHE_TTL=3600
FEATURE_BETA_DASHBOARD=false
FEATURE_API_V2=true
```

---

Quick Start
-----------

[](#quick-start)

```
use MugiWara\FeatureFlags\Facades\Feature;

// Check a flag
if (Feature::isEnabled('beta_dashboard')) {
    // new experience
}

// Conditional with fallback
$result = Feature::when('beta_dashboard',
    fn () => view('dashboard.beta'),
    fn () => view('dashboard.classic')
);

// Toggle at runtime (database driver)
Feature::enable('beta_dashboard');
Feature::disable('beta_dashboard');
```

---

Drivers
-------

[](#drivers)

### Config Driver

[](#config-driver)

Reads flags from the `flags` array in `config/features.php`. Flags can be toggled via environment variables. Changes made with `enable()`/`disable()` are held in memory only and **not persisted** across requests.

```
FEATURE_DRIVER=config
```

Best for: simple setups, CI/CD-driven toggles, apps that don't need runtime changes.

### Database Driver

[](#database-driver)

Stores flags in the `feature_flags` table. Supports full runtime CRUD, caching, multi-tenancy, segments, and percentage rollouts.

```
FEATURE_DRIVER=database
```

**Database schema:**

ColumnTypeDescription`id`bigintPrimary key`name`stringFeature flag name (indexed)`enabled`booleanOn/off state (default: `false`)`tenant_id`string, nullableTenant scope (indexed). `NULL` = global`description`text, nullableHuman-readable description`metadata`json, nullablePercentage rollout, user segments, etc.`created_at`timestamp`updated_at`timestampUnique constraint on `(name, tenant_id)`.

**Caching:** The database driver caches flag lookups using Laravel's cache. The cache key format is `{prefix}{name}{:tenant_id}`. An observer (`FeatureFlagObserver`) automatically clears the cache when a `FeatureFlag` model is saved or deleted.

### Custom Drivers

[](#custom-drivers)

Implement the `FeatureDriver` interface:

```
use MugiWara\FeatureFlags\Contracts\FeatureDriver;

class RedisDriver implements FeatureDriver
{
    public function isEnabled(string $feature): bool
    {
        return (bool) Redis::get("feature:{$feature}");
    }

    public function enable(string $feature): void
    {
        Redis::set("feature:{$feature}", 1);
    }

    public function disable(string $feature): void
    {
        Redis::set("feature:{$feature}", 0);
    }
}
```

Register in a service provider:

```
use MugiWara\FeatureFlags\Contracts\FeatureDriver;

$this->app->singleton(FeatureDriver::class, RedisDriver::class);
```

---

Usage
-----

[](#usage)

### Facade

[](#facade)

```
use MugiWara\FeatureFlags\Facades\Feature;

Feature::isEnabled('beta_dashboard');    // bool
Feature::isDisabled('beta_dashboard');   // bool

Feature::enable('beta_dashboard');       // void (database driver persists)
Feature::disable('beta_dashboard');      // void

// Conditional execution with optional fallback
Feature::when('premium_content',
    fn () => showPremiumContent(),
    fn () => showFreeContent()
);

// Get all flags as ['name' => bool]
Feature::all();

// User-aware check (respects segments & percentage rollout)
Feature::forUser($user, 'beta_dashboard');
```

### Dependency Injection

[](#dependency-injection)

Inject the `FeatureManager` contract:

```
use MugiWara\FeatureFlags\Contracts\FeatureManager;

class DashboardController extends Controller
{
    public function __construct(protected FeatureManager $features) {}

    public function index()
    {
        if ($this->features->isEnabled('beta_dashboard')) {
            return view('dashboard.beta');
        }

        return view('dashboard.classic');
    }
}
```

### Route Middleware

[](#route-middleware)

The package registers a `feature` route middleware automatically. No manual Kernel registration needed.

**Syntax:** `feature:{flag_name},{response_type}`

```
// 404 Not Found (default)
Route::get('/beta', [BetaController::class, 'index'])
    ->middleware('feature:beta_dashboard');

// 403 Forbidden
Route::get('/admin/reports', [ReportController::class, 'index'])
    ->middleware('feature:advanced_reporting,forbidden');

// JSON error response {"error": "Feature not available"} (ideal for APIs)
Route::get('/api/v2/users', [UserController::class, 'index'])
    ->middleware('feature:api_v2,json');

// Redirect to home route with flash message
Route::get('/premium', [PremiumController::class, 'index'])
    ->middleware('feature:premium_content,redirect');
```

**Response types:**

TypeHTTP StatusResponse*(default)*404Not Found`forbidden`403"This feature is disabled."`json`404`{"error": "Feature not available"}``redirect`302Redirect to `home` route with `error` flash**Protect a route group:**

```
Route::middleware('feature:api_v2,json')->prefix('v2')->group(function () {
    Route::get('/users', [UserV2Controller::class, 'index']);
    Route::get('/posts', [PostV2Controller::class, 'index']);
});
```

### Blade Directives

[](#blade-directives)

Seven Blade directives are registered automatically:

```
{{-- Show content when feature is enabled --}}
@feature('beta_dashboard')

@endfeature

{{-- With an else branch --}}
@feature('beta_dashboard')

@elsefeature

@endfeature

{{-- Show content when feature is disabled --}}
@unlessfeature('maintenance_mode')
    Go to Dashboard
@endunlessfeature

{{-- Show content when ANY of the listed features is enabled --}}
@featureany(['beta_dashboard', 'new_ui', 'premium_content'])
    At least one new feature is active!
@endfeatureany
```

**Full example:**

```

    @feature('welcome_banner')

            New features are here! Check out the updated dashboard.

    @elsefeature
        Welcome back.
    @endfeature

    @feature('maintenance_mode')
        We're under maintenance. Check back later.
    @endfeature

    @unlessfeature('maintenance_mode')
        Go to Admin
    @endunlessfeature

    @featureany(['beta_dashboard', 'premium_content'])

            You have access to new features!

    @endfeatureany

```

### Model Protection

[](#model-protection)

Use the `HasFeatureFlags` trait to make an Eloquent model invisible when its feature is disabled. A global scope is registered automatically at boot — no extra setup needed.

```
use Illuminate\Database\Eloquent\Model;
use MugiWara\FeatureFlags\Concerns\HasFeatureFlags;

class PremiumContent extends Model
{
    use HasFeatureFlags;

    public function getFeatureFlag(): string
    {
        return 'premium_content';
    }
}
```

**When `premium_content` is disabled:**

```
PremiumContent::all();              // Empty collection
PremiumContent::find(1);            // null
PremiumContent::where(...)->get();  // Empty collection
```

**When enabled:** all queries work normally.

**Instance methods:**

```
$content->featureIsEnabled();    // bool
$content->featureIsDisabled();   // bool

$content->whenFeatureEnabled(
    fn () => $content->render(),        // runs if enabled
    fn () => 'Feature not available'    // optional fallback
);
```

**Bypass the scope:**

```
PremiumContent::withoutGlobalScopes()->get();
```

### Service Method Gating

[](#service-method-gating)

Use the `FeatureGuarded` trait to guard individual methods behind feature flags.

```
use MugiWara\FeatureFlags\Concerns\FeatureGuarded;

class PaymentService
{
    use FeatureGuarded;

    // Throws FeatureDisabledException (403) when disabled
    public function processPayment(array $data): void
    {
        $this->requireFeature('online_payments');
        // ... process payment
    }

    // Returns null silently when disabled
    public function sendReceipt(string $email): ?bool
    {
        return $this->whenFeature('email_receipts', function () use ($email) {
            Mail::to($email)->send(new ReceiptMail());
            return true;
        });
    }

    // Throws FeatureDisabledException, then runs callback
    public function refund(int $orderId): array
    {
        return $this->executeIfFeature('online_payments', function () use ($orderId) {
            return $this->processRefund($orderId);
        });
    }
}
```

MethodWhen disabled`requireFeature(string $feature)`Throws `FeatureDisabledException``whenFeature(string $feature, callable $callback)`Returns `null``executeIfFeature(string $feature, callable $callback)`Throws `FeatureDisabledException`### Relationship Scoping

[](#relationship-scoping)

Use the `FeatureScopedRelation` trait to gate Eloquent relationships. When the feature is disabled, the relationship returns empty results — no crashes, no null checks needed.

```
use MugiWara\FeatureFlags\Concerns\FeatureScopedRelation;

class User extends Model
{
    use FeatureScopedRelation;

    public function reports()
    {
        return $this->hasManyWithFeature('advanced_reporting', Report::class);
    }

    public function premiumSubscription()
    {
        return $this->belongsToWithFeature('premium_access', Subscription::class);
    }

    public function betaProfile()
    {
        return $this->hasOneWithFeature('beta_profile', BetaProfile::class);
    }

    public function teams()
    {
        return $this->belongsToManyWithFeature('team_feature', Team::class);
    }
}
```

**Supported relationship types:**

MethodRelationship`hasManyWithFeature($flag, $related, ...)`HasMany`hasOneWithFeature($flag, $related, ...)`HasOne`belongsToWithFeature($flag, $related, ...)`BelongsTo`belongsToManyWithFeature($flag, $related, ...)`BelongsToMany`featureRelation($flag, $callback)`Any relationship (generic)All methods accept the same optional parameters as their native Laravel counterparts (`$foreignKey`, `$localKey`, etc.).

**Generic approach:**

```
public function customRelation()
{
    return $this->featureRelation('some_feature', function () {
        return $this->hasMany(SomeModel::class);
    });
}
```

---

User Segments
-------------

[](#user-segments)

Target features to specific user groups. Requires the database driver.

**Step 1:** Implement `HasFeatureSegments` on your User model:

```
use MugiWara\FeatureFlags\Contracts\HasFeatureSegments;

class User extends Authenticatable implements HasFeatureSegments
{
    public function getFeatureSegments(): array
    {
        return array_filter([
            $this->role,    // 'admin', 'editor', 'viewer'
            $this->plan,    // 'free', 'pro', 'enterprise'
            'all_users',
        ]);
    }
}
```

**Step 2:** Create a flag with segment restrictions in the `metadata`:

```
use MugiWara\FeatureFlags\Models\FeatureFlag;

FeatureFlag::create([
    'name'     => 'beta_dashboard',
    'enabled'  => true,
    'metadata' => [
        'segments' => ['beta_testers', 'admin'],
    ],
]);
```

**Step 3:** Use `forUser()` to check:

```
Feature::forUser($user, 'beta_dashboard');
// Returns true only if the user's segments intersect with ['beta_testers', 'admin']
```

**How it works:** If the flag's `metadata.segments` array is empty or not set, `forUser()` returns `true` for all users (when the flag is enabled). If segments are defined, the user must belong to at least one of them.

---

Percentage Rollouts
-------------------

[](#percentage-rollouts)

Gradually roll out a feature to a percentage of users. Uses consistent hashing (CRC32) so the same user always gets the same result — no flickering.

```
use MugiWara\FeatureFlags\Strategies\PercentageRolloutStrategy;

$strategy = new PercentageRolloutStrategy();

// Enable for 25% of users
if ($strategy->shouldEnable('new_checkout', auth()->id(), 25)) {
    return view('checkout.new');
}

return view('checkout.classic');
```

**How it works:**

```
bucket = abs(crc32("{feature}:{identifier}")) % 100
enabled = bucket < percentage

```

- `percentage = 0` — always disabled
- `percentage = 100` — always enabled
- `percentage = 25` — enabled for ~25% of users, deterministically

**Debug a user's bucket:**

```
$bucket = $strategy->getBucket('new_checkout', $userId);
// Returns 0–99
```

**Store percentage in the database:**

```
FeatureFlag::create([
    'name'     => 'new_checkout',
    'enabled'  => true,
    'metadata' => ['percentage' => 25],
]);
```

When using `Feature::forUser($user, 'new_checkout')`, the database driver automatically reads the `metadata.percentage` and applies the rollout strategy.

---

Multi-Tenancy
-------------

[](#multi-tenancy)

The database driver has built-in multi-tenant support. Tenant-specific flags override global flags.

**Set tenant context** (typically in middleware):

```
use MugiWara\FeatureFlags\Drivers\DatabaseDriver;

class SetTenantContext
{
    public function handle(Request $request, Closure $next)
    {
        $tenantId = $request->user()?->tenant_id;
        app(DatabaseDriver::class)->setTenant($tenantId);
        return $next($request);
    }
}
```

**Flag resolution order:**

1. Tenant-specific flag (`tenant_id = $tenantId`) — checked first
2. Global flag (`tenant_id IS NULL`) — fallback

**Example:**

```
# Global: beta_dashboard is OFF for everyone
php artisan feature:create beta_dashboard

# Override: ON for tenant "acme_corp"
php artisan feature:enable beta_dashboard --tenant=acme_corp
```

Acme Corp users see the beta dashboard. All other tenants see the classic dashboard.

---

Artisan Commands
----------------

[](#artisan-commands)

All commands support the `--tenant` option when using the database driver.

### `feature:list`

[](#featurelist)

List all flags and their status.

```
php artisan feature:list
php artisan feature:list --tenant=acme_corp
```

### `feature:create`

[](#featurecreate)

Create a new feature flag (database driver only).

```
php artisan feature:create beta_dashboard
php artisan feature:create beta_dashboard --enabled
php artisan feature:create beta_dashboard --enabled --description="New dashboard experience"
php artisan feature:create beta_dashboard --enabled --percentage=25
php artisan feature:create beta_dashboard --tenant=acme_corp
```

OptionDescription`--enabled`Create in enabled state (default: disabled)`--description=`Human-readable description`--percentage=`Percentage rollout (1–99)`--tenant=`Scope to a specific tenant### `feature:enable`

[](#featureenable)

Enable an existing flag.

```
php artisan feature:enable beta_dashboard
php artisan feature:enable beta_dashboard --tenant=acme_corp
```

### `feature:disable`

[](#featuredisable)

Disable an existing flag.

```
php artisan feature:disable beta_dashboard
php artisan feature:disable beta_dashboard --tenant=acme_corp
```

---

Events
------

[](#events)

Two events are dispatched when flags are toggled via `Feature::enable()` or `Feature::disable()`:

```
use MugiWara\FeatureFlags\Events\FeatureEnabled;
use MugiWara\FeatureFlags\Events\FeatureDisabled;
```

Both events have the same properties:

PropertyTypeDescription`$feature``string`The feature flag name`$tenantId``?string`Tenant context at the time of change, or `null`**Listen for changes:**

```
use Illuminate\Support\Facades\Event;
use MugiWara\FeatureFlags\Events\FeatureEnabled;
use MugiWara\FeatureFlags\Events\FeatureDisabled;

Event::listen(FeatureEnabled::class, function (FeatureEnabled $event) {
    Log::info("Feature enabled: {$event->feature}", ['tenant' => $event->tenantId]);
});

Event::listen(FeatureDisabled::class, function (FeatureDisabled $event) {
    Log::info("Feature disabled: {$event->feature}", ['tenant' => $event->tenantId]);
});
```

---

Exception Handling
------------------

[](#exception-handling)

The `FeatureDisabledException` is thrown by `FeatureGuarded::requireFeature()` and `FeatureGuarded::executeIfFeature()`.

**Default rendering:**

Request typeResponseJSON (`expectsJson()`)`{"error": "This feature is currently disabled."}` with HTTP 403Web`abort(403, 'This feature is currently disabled.')`**Custom handling** in your exception handler:

```
use MugiWara\FeatureFlags\Exceptions\FeatureDisabledException;

// In bootstrap/app.php (Laravel 11+) or app/Exceptions/Handler.php
$this->renderable(function (FeatureDisabledException $e, Request $request) {
    if ($request->expectsJson()) {
        return response()->json(['message' => $e->getMessage()], 403);
    }

    return redirect()->back()->with('warning', $e->getMessage());
});
```

---

Custom Resolvers
----------------

[](#custom-resolvers)

Override all feature checks with custom logic. When a resolver is set, it takes priority over the driver.

```
use MugiWara\FeatureFlags\Facades\Feature;

// Set a custom resolver
Feature::resolveUsing(function (string $feature): bool {
    return ExternalService::isEnabled($feature);
});

// Reset to default (driver-based) behavior
Feature::resolveUsing(null);
```

---

FeatureFlag Model
-----------------

[](#featureflag-model)

The `FeatureFlag` Eloquent model provides query scopes and metadata helpers.

```
use MugiWara\FeatureFlags\Models\FeatureFlag;
```

**Query scopes:**

```
FeatureFlag::global()->get();            // WHERE tenant_id IS NULL
FeatureFlag::forTenant('acme')->get();   // WHERE tenant_id = 'acme'
FeatureFlag::enabled()->get();           // WHERE enabled = true
FeatureFlag::disabled()->get();          // WHERE enabled = false
```

**Metadata helpers:**

```
$flag = FeatureFlag::where('name', 'new_ui')->first();

$flag->hasPercentageRollout();                // bool
$flag->getRolloutPercentage();                // int|null
$flag->getAllowedSegments();                  // string[]
$flag->isAvailableForSegment('beta_testers'); // bool
```

**Creating flags with metadata:**

```
FeatureFlag::create([
    'name'        => 'new_checkout',
    'enabled'     => true,
    'description' => 'Redesigned checkout flow',
    'metadata'    => [
        'percentage' => 25,
        'segments'   => ['beta_testers', 'staff'],
    ],
]);
```

---

Architecture
------------

[](#architecture)

```
Feature Facade
    └── FeatureManager (implements Contracts\FeatureManager)
            ├── Custom resolver (takes priority when set)
            └── FeatureDriver (interface)
                    ├── ConfigDriver (reads from config/features.php)
                    └── DatabaseDriver
                            ├── Cache layer (configurable TTL)
                            ├── Multi-tenant query scoping
                            ├── FeatureFlag Eloquent model
                            ├── FeatureFlagObserver (auto cache invalidation)
                            └── PercentageRolloutStrategy (CRC32 hashing)

Protection layers:
    ├── RequireFeature middleware (HTTP routes)
    ├── Blade directives (@feature, @elsefeature, @unlessfeature, @featureany)
    ├── HasFeatureFlags trait (Eloquent global scopes)
    ├── FeatureGuarded trait (service method gating)
    └── FeatureScopedRelation trait (relationship scoping)

```

**Key contracts:**

ContractDescription`Contracts\FeatureManager`Main API: `isEnabled`, `enable`, `disable`, `when`, `all`, `forUser`, `resolveUsing``Contracts\FeatureDriver`Storage backend: `isEnabled`, `enable`, `disable``Contracts\HasFeatureSegments`User model interface: `getFeatureSegments(): array`---

Testing
-------

[](#testing)

```
composer test
```

The test suite uses Orchestra Testbench and covers all features: facade, middleware, Blade directives, model protection, service gating, relationship scoping, user segments, percentage rollouts, multi-tenancy, events, caching, and Artisan commands.

---

License
-------

[](#license)

MIT License.

---

**Author:** Sohaib Bilal —

###  Health Score

35

—

LowBetter than 80% of packages

Maintenance82

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

93d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/fe819ce13d2b7e19d34630b2a40a8595a9fdec77fbc109e322d0d9c20a6a15a3?d=identicon)[sohaibhbill](/maintainers/sohaibhbill)

---

Tags

laravelrolloutfeature-flagsfeature-toggles

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/mugiwara-laravel-feature-flags/health.svg)

```
[![Health](https://phpackages.com/badges/mugiwara-laravel-feature-flags/health.svg)](https://phpackages.com/packages/mugiwara-laravel-feature-flags)
```

###  Alternatives

[yajra/laravel-datatables-oracle

jQuery DataTables API for Laravel

4.9k33.8M339](/packages/yajra-laravel-datatables-oracle)[spatie/laravel-enum

Laravel Enum support

3655.4M31](/packages/spatie-laravel-enum)[psalm/plugin-laravel

Psalm plugin for Laravel

3274.9M308](/packages/psalm-plugin-laravel)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[datomatic/nova-enum-field

A Laravel Nova PHP 8.1 enum field with filters

20134.2k](/packages/datomatic-nova-enum-field)[bjuppa/laravel-blog

Add blog functionality to your Laravel project

483.3k2](/packages/bjuppa-laravel-blog)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
