Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Feature Flags Laravel Package

ylsideas/feature-flags

An extendable Laravel package for managing feature flags in code, routes, Blade views, task scheduling, and validations—making continuous integration and deployment easier by safely guarding features behind toggles.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require ylsideas/feature-flags:^3.0

php artisan vendor:publish \
  --provider="YlsIdeas\FeatureFlags\FeatureFlagsServiceProvider" \
  --tag=config

Version 3 is the current line (v3.1.0 added Laravel 13 support). If you're on Laravel 9/PHP 8 you want ^2.0; version 1 is unsupported.

2. Know what you got by default

The published config/features.php ships with an in-memory gateway that reads a .features.php file at the project root. That means you can start flagging code with zero database work — commit .features.php for local/dev defaults and treat it as code, not runtime state.

3. First use case — the three-line version

use YlsIdeas\FeatureFlags\Facades\Features;

if (Features::accessible('new-checkout')) {
    // new path
} else {
    // legacy path
}

Do this for the first flag only at the top of a controller or Blade view. Resist wrapping ten call sites on day one — pick one blast radius and confirm the plumbing works.

4. Where to look first

  • config/features.php — the gateways and pipeline definitions are the whole mental model of this package.
  • php artisan feature:state my-flag — prints the current state of a flag; the fastest sanity check when a flag "isn't working".
  • The docs at feature-flags.docs.ylsideas.co — the README is deliberately thin; the integration surface (middleware, Blade, validation, scheduler, Eloquent mixin) is only documented there.
  • CHANGELOG.md / releases — this package ships features fast (debug system, query-builder mixin, maintenance mode, fakes all arrived as 2.x minors). Skim it before assuming a capability is missing.

Implementation Patterns

Model the pipeline before you model the flags

The v2+ architecture replaced "repositories" with a pipeline of gateways. A flag is resolved by walking gateways in order; each gateway can be configured to only handle certain flags via filters, and can carry its own cache. This is the single most important design decision you'll make.

A pragmatic production pipeline:

// config/features.php (shape, not verbatim)
'pipeline' => [
    'database' => ['cache' => 60],  // runtime overrides an admin/ops team flips
    'in_memory' => [],              // .features.php: per-environment defaults, committed
],

This gives you: ops can flip a flag from a console command or dashboard without a deploy; engineers can still set a deterministic default in code review.

Make toggling part of your ops runbook

Gateways implementing the toggleable interface (database, redis) are writable at runtime. Wire the commands into your runbook:

php artisan feature:on new-checkout
php artisan feature:off new-checkout
php artisan feature:state new-checkout

If feature:on seems to do nothing, your pipeline is resolving from a non-toggleable gateway (e.g. in-memory) first — see Gotchas.

Route-level gating for whole surfaces

Middleware is the right tool when the feature is a page, not a branch in a page:

Route::get('/reports/beta', ReportsController::class)
    ->middleware('feature:beta-reports');

Since v2.3 the middleware can be configured with a message, so you control what a user sees when they hit a gated route rather than exposing a generic denial. Pair this with a navigation check using Features::accessible() so you never link to a route the user can't reach.

Eloquent query-builder mixin for data-shape flags

For flags that change what data exists rather than which view renders, use the query-builder mixin added in v2.1:

Post::query()
    ->whenFeatureIsAccessible('rich-posts', fn ($q) => $q->with('media'))
    ->whenFeatureIsNotAccessible('rich-posts', fn ($q) => $q->whereNull('legacy_only'))
    ->get();

This keeps conditional query logic out of your controllers and makes it greppable later when the flag is retired.

Testing — fake aggressively, fake early

use YlsIdeas\FeatureFlags\Facades\Features;

public function setUp(): void
{
    parent::setUp();
    Features::fake(['new-checkout' => true, 'beta-reports' => false]);
}

Notes that matter in practice:

  • Fakes stack (v2.6 allowed multiple sequential fakes), so scope them tightly and reset in tearDown rather than relying on one global fake per suite.
  • Because faking is supported directly on the facade, you do not need to bind a fake gateway into the container — avoid hand-rolling test doubles.
  • Add at least one test asserting the off branch still works. The most common flag bug is an off-path that nobody exercised until rollout day.

Per-user / per-tenant rollout via the Gate gateway

The gate-based driver lets you express flag access as a Laravel Gate, which is how you get percentage or segment rollouts without new infrastructure:

  • Define a gate per flag (or one generic gate resolving by name).
  • Configure the gate gateway in the pipeline.
  • Features::accessible('flag') then respects Gate::allows() for the authenticated user.

Caveat: gate-driven flags are inherently request/user-scoped. Don't put them in the same pipeline position you use for global kill-switches.

Debugging and expiration hooks

v2.1 added a system for debugging features that are accessed and a handler for when features should be expired. Use both:

  • Debug logging during a rollout window tells you which flags were actually hit — that's your evidence for deleting the branch.
  • An expiration handler gives you a forcing function; a flag with a date is a flag that gets removed.

Maintenance mode as a flag

Since v2.4 you can drive maintenance mode with flags. This is a good pattern for "degrade gracefully per tenant" rather than a binary artisan down that hits everyone.

Gotchas and Tips

Cached gateway results were not always booleans. v3.0.1 explicitly fixed this ("force casting cache result to boolean"). On 3.0.0 or earlier, a cached "0" or "false" string can be truthy in a loose check. Pin ^3.0.2 and still prefer Features::accessible() over casting raw gateway values yourself.

update_on_resolve is easy to misread. It controls whether resolving a flag writes back to the underlying gateway. The Chain gateway historically ignored it (fixed in 1.2.1), which is a good reminder: if you change that setting, test the chain case specifically, not just a single gateway. If you're unsure, leave it at the default and let the pipeline order do the work.

A non-toggleable gateway in the pipeline silently swallows your toggles. In-memory/config gateways resolve from files and config; feature:on can't write to them. Check ordering with feature:state before you file a bug.

Long-running processes hold stale flag state. Under Octane or in queue workers, gateway caches and resolved values can outlive a toggle. Either keep the cache TTL short for runtime-controlled flags, clear the feature cache as part of your toggle procedure, or resolve flags from an uncached gateway in worker contexts.

Unregistered/unknown flags resolve to off. That's the safe default, but it means a typo in a flag name is indistinguishable from an intentional off state. Centralise flag names in a constants class or enum and reference those everywhere — middleware strings, Blade, validation, and queries.

Validation semantics are "must be accessible". The validation rule was inverted in 1.0.x and fixed in 1.1.0. If your form validation for a flagged field behaves backwards, check you're not on an ancient version and check whether your pipeline is returning the value you think it is.

Don't let gated middleware break API contracts. A flag middleware rejects the request; if the same route serves JSON and HTML clients, confirm the response shape per client (the middleware message config from v2.3 is your lever). Otherwise a flag flip turns a clean 200 into an opaque HTML error in a mobile client.

Retire flags on a schedule, not on vibes. Combine the debug-access log with the expiration handler: pick a date at creation, check the debug log at that date, delete the branch. Flags that live forever become unreadable conditional soup and are the main reason teams turn against feature flagging.

Extension points worth knowing: custom gateways (implement the gateway contract, add toggleable if writable), gateway filters (scope a gateway to a flag namespace), per-gateway caches, and the pipeline composition itself. Most "the package can't do X" problems are actually "write a small gateway that does X and drop it in the pipeline."

Upgrading between majors is not always free. v1→v2 renamed repositories to gateways, introduced the pipeline, and switched the default config to in-memory with .features.php. If you inherit a v1 app, use UPGRADE.md; v2→v3 needs no real work (only type-hint tightening), so ^3.0 is the right target on any actively maintained Laravel app.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin