tax16/feature-flag
tax16/feature-flag is a lightweight Laravel package for defining and checking feature flags in your application. Toggle features on or off with simple configuration, keeping releases safer and functionality easy to manage. Ideal for gradual rollouts and environment-specific behavior.
Install the package and publish its config so flag definitions are versioned with your app:
composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-config
Open config/<package>.php first — that is where flags, their default state, and any driver/resolver settings live. If the package supports DB-backed flags, publish and run its migrations before writing any flag checks, otherwise you will end up with two sources of truth.
First use case: protect one in-flight route rather than retrofitting the whole app. Pick a single controller method, declare the flag, and apply the new route-switching attribute:
#[FeaturesFlagSwitchRoute(flag: 'new-checkout')]
public function checkout() { /* ... */ }
Then verify it in both directions — flag on, flag off — before touching a second route. Confirm the exact attribute namespace (use ...\Attributes\FeaturesFlagSwitchRoute;) from the package's src/Attributes directory; the pluralised Features in the class name is easy to mistype.
Route-level switching. The v1.1.1 #[FeaturesFlagSwitchRoute] attribute is the lightest-touch integration: no middleware aliases, no if branches in the controller. Use it as the default for new endpoints and for cutting over rewritten controllers, keeping the old route registered under a separate flag or as the fallback.
Class and method blocking. The paired blocking attribute lets you disable a whole service or a single method without editing its body. This is the right tool for killing a noisy integration (an outbound webhook sender, a slow report generator) during an incident: decorate the class, leave the implementation untouched, flip the flag off. Prefer method-level blocking when only one code path is suspect — class-level blocking is blunt and will also disable constructor-adjacent behaviour you may still need.
Layered flag strategy. Combine the two features:
Keeping these separate means an incident response can disable the expensive collaborator while leaving the page rendering, rather than 404-ing the route.
Testing. Write each flagged feature test twice — once with the flag forced on and once off — using the package's test helper or by overriding the flag in the config before the app boots. Attribute-based routing is resolved early, so a mid-test config()->set() is often too late; set flags in defineEnvironment() / setUp() instead.
Rollout workflow. Keep flag names stable and descriptive (checkout.v2, not flag1), default new flags to off in config, and delete the attribute in the same PR that removes the flag once the rollout completes. Treat the flag name as a contract referenced from three places (config, attribute, tests).
Route caching. Attributes that gate route registration are typically evaluated when routes are collected. After changing a flag, run php artisan route:clear (and rebuild route:cache in deploy) — otherwise production will keep serving the previously compiled route table and the flag will look broken. Verify this behaviour explicitly against your deploy pipeline; it is the single most likely source of "the flag didn't work in prod" reports.
Constructor injection can bypass blocking. If class-level blocking is implemented via a container binding or proxy, objects already resolved through a typed constructor parameter in a service provider may escape the block. Grep for new Foo( and direct app(Foo::class) calls when disabling a class, and prefer resolving through the container.
Blocking fails loudly or silently — know which. Decide whether a blocked method throws, returns null, or short-circuits to a no-op. A silent no-op on a method with a return type will produce a TypeError at the call site, which is confusing to debug. Check the package's exception class and catch it at the boundary.
Flag resolution cost. If flags are resolved per attribute invocation and are DB-backed, a page with several flagged routes can issue repeated queries. Cache the resolver per request, or use the config driver for hot paths.
Attribute typos fail at route-registration time, not at request time, which is good — but only if the class autoloads. A missing use statement on an attribute silently does nothing in PHP for some constructs; confirm the attribute is actually applied by toggling its flag and observing a 404/exception.
Extension points. Look for a resolver/manager contract bound in the service provider — that is where to plug in LaunchDarkly, Unleash, or an internal flag service instead of the default config/DB driver. Keep custom resolvers request-scoped and memoised.
Cleanup discipline. Attributes are invisible in a controller's body, so abandoned flags accumulate silently. Add a CI check that every flag name referenced in an attribute exists in config, and log a warning for flags that have been at 100% for more than a release cycle.
How can I help you explore Laravel packages today?