joshuaestes/feature-toggle
A feature toggle (feature flag) package for Laravel that lets you enable or disable application features at runtime. Define toggles in configuration, check them cleanly in your code, and roll out or hide functionality without redeploying.
Install and wire it up (Laravel 5-era package):
composer require joshuaestes/feature-toggle
This package predates Laravel package auto-discovery (which landed in 5.5), so register it manually in config/app.php:
'providers' => [
// ...
JoshuaEstes\FeatureToggle\FeatureToggleServiceProvider::class,
],
'aliases' => [
// ...
'FeatureToggle' => JoshuaEstes\FeatureToggle\Facades\FeatureToggle::class,
],
Then publish the config and read it first — it is the primary documentation for this package:
php artisan vendor:publish --provider="JoshuaEstes\FeatureToggle\FeatureToggleServiceProvider"
Where to look first:
config/feature-toggle.php — how features are declared, their default on/off state, and which storage driver is used. This file tells you more than the README will.vendor/joshuaestes/feature-toggle/src/ — read the manager class and the facade. With 24 stars and no dependents there is no community knowledge to fall back on; the source is the documentation.composer.json in the package — check the illuminate/* constraints. If you are on Laravel 9+, expect to need a fork or a replace constraint to install it at all.First use case — put a new feature behind a toggle before you build anything else:
if (FeatureToggle::isEnabled('new-checkout')) {
return $this->newCheckout($request);
}
return $this->legacyCheckout($request);
And in the config:
'features' => [
'new-checkout' => [
'enabled' => env('FEATURE_NEW_CHECKOUT', false),
],
],
The whole value of the package is turning that boolean into a named, centrally-managed, overridable concept rather than an env() call scattered through the codebase.
Config-driven toggles for deploy-time switches. The most common and safest pattern. Define the feature once in config/feature-toggle.php, read it everywhere through the facade. Toggles that never change at runtime can safely live in config, and they survive config:cache.
Database-backed toggles for runtime flipping. If the package's driver supports a persistent store, publish/run its migration and back the manager with a features table. This lets ops or an admin panel flip a flag without a deploy. The trade-off is a DB read per check — wrap your access in a small cached repository of your own rather than calling the facade in a loop:
class Toggles
{
public function __construct(private FeatureToggle $toggle) {}
public function enabled(string $feature): bool
{
return Cache::rememberForever("toggle:{$feature}", fn () => $this->toggle->isEnabled($feature));
}
}
Middleware for route-level gating. Keep controllers clean by resolving the toggle in middleware and aborting or redirecting:
class EnsureFeatureEnabled
{
public function handle($request, Closure $next, string $feature)
{
abort_unless(FeatureToggle::isEnabled($feature), 404);
return $next($request);
}
}
Route::get('/beta', BetaController::class)->middleware('feature:beta-dashboard');
Blade-level checks for UI. Hide nav items, buttons, and partials behind the same toggle so the back end and front end never disagree:
@if (FeatureToggle::isEnabled('new-checkout'))
@include('checkout.new')
@endif
Wrap it behind your own interface. Because the package is small and unmaintained, inject an application-owned FeatureFlags contract and adapt the package inside it. This gives you one file to change if you swap the library later, and it makes tests trivial:
interface FeatureFlags { public function on(string $feature): bool; }
class PackageFeatureFlags implements FeatureFlags
{
public function on(string $feature): bool
{
return FeatureToggle::isEnabled($feature);
}
}
Testing pattern. In setUp() (or a Pest beforeEach), force the flags you need rather than relying on config values:
FeatureToggle::enable('new-checkout'); // or override the config array
If the package offers no test double, bind a fake implementation of your own FeatureFlags contract in the container. Never let tests depend on the developer's local .env.
Rollout pattern. Use a stable, permanent feature name (e.g. new-checkout) rather than tickets or dates. Treat removal of the toggle as part of the feature's definition of done — schedule a cleanup so dead if branches don't accumulate. This library does not do percentage rollouts or user targeting for you; if you need that, hash a user ID and combine it with the toggle yourself.
It is a 2015 package. Last release December 2015, zero dependents, 24 stars. Expect Laravel 5.0/5.1 illuminate constraints, no auto-discovery, no typed properties, and possible friction on PHP 8+. Before adopting, run composer require in a scratch branch; if it won't resolve, vendor the source or fork it into your app rather than fighting constraints. For a feature-flag need this simple, writing a 100-line internal implementation is often the better call.
config:cache will freeze your toggles. If toggles live in config, a flip requires a config cache clear and redeploy — which defeats the purpose of a runtime toggle. Keep deploy-time flags in config and runtime flags in the database, and be explicit about which is which.
Decide fail-open vs fail-closed for unknown features. A typo in a feature name (new-checout) silently returns whatever the manager's default is. Pick fail-closed for anything user-facing and add a check in development, or log a warning when a requested feature isn't defined in the config.
Case sensitivity and naming. Feature names are array keys / string lookups; NewCheckout and new-checkout are different flags on a case-sensitive store. Standardize on kebab-case and constants.
Service provider and facade must both be registered. Missing the facade alias gives a confusing "Class 'FeatureToggle' not found" even though the provider is loaded. In Lumen, skip the facade entirely and resolve the manager from the container.
Database driver means migrations and cache invalidation. If you go DB-backed, you own the migration (the package may provide one but not auto-load it on older Laravel), and you must invalidate any cache you layer on top whenever a flag flips. A stale cache here means a "disabled" feature stays live.
Extension points are where you'd expect. The manager typically resolves a driver/store; if you need a new backend (Redis, Unleash, LaunchDarkly), that is the seam to implement against rather than scattering if statements. Read the manager class before writing anything custom — the surface area is small.
Don't let toggles leak into domain logic. Pass a resolved boolean into your services/actions; don't inject the facade deep into business code. This keeps the flag removable and your domain testable without the package installed.
How can I help you explore Laravel packages today?