Laravel Toggle is a lightweight feature flag package focused on global on/off switches. Laravel's first-party Pennant package is built for user-segmented rollouts and A/B testing; Laravel Toggle is intentionally simpler and skips user resolution entirely, leaving you with flags controlled by .env, the database, or a mix of the two.
Installation
The package requires PHP 8.2+ and Laravel 11, 12, or 13.
composer require offload-project/laravel-toggle php artisan vendor:publish --tag=toggle-config
If you plan to use the database driver, publish and run the migrations:
php artisan vendor:publish --tag=toggle-migrationsphp artisan migrate
Defining and Checking Toggles
Flags live in config/toggle.php and can be backed by environment variables:
'flags' => [ 'comments' => env('TOGGLE_COMMENTS', true), 'related-articles' => env('TOGGLE_RELATED_ARTICLES', false),],
You check them through the Toggle facade. An article controller that conditionally loads relationships can branch on a flag without polluting the query:
use OffloadProject\Toggle\Facades\Toggle; class ArticleController{ public function show(string $slug) { $article = Article::where('slug', $slug) ->when(Toggle::active('comments'), fn ($q) => $q->with('comments.author')) ->firstOrFail(); return view('articles.show', [ 'article' => $article, 'related' => Toggle::active('related-articles') ? RelatedArticles::for($article) : collect(), ]); }}
Toggle::inactive() is handy for guarding behaviour that should only run while a feature is off, like falling back to a plain email digest when the richer newsletter pipeline is disabled:
if (Toggle::inactive('newsletter-v2')) { Mail::to($subscriber)->queue(new WeeklyDigest($articles));}
Blade templates get dedicated directives so you don't have to wrap conditionals manually. A typical case is rendering a comment thread under an article only while the feature is enabled:
@toggle('comments') <livewire:article.comments :article="$article" />@elsetoggle <p class="text-sm text-gray-500">Comments are closed on this post.</p>@endtoggle
If you prefer type-safe identifiers, you can use a backed enum anywhere a flag name is accepted. Centralising flag names in a Feature enum keeps them autocompletable and easy to grep when it is time to retire one:
enum Feature: string{ case Comments = 'comments'; case RelatedArticles = 'related-articles'; case Paywall = 'paywall';} if (Toggle::active(Feature::Paywall) && $article->isPremium()) { return view('articles.paywall', compact('article'));}
Drivers
Laravel Toggle ships with two storage drivers, selected through TOGGLE_DRIVER:
TOGGLE_DRIVER=config # Read-only, uses config/toggle.php flagsTOGGLE_DRIVER=database # Read-write, falls back to config
The config driver is read-only at runtime because its values are sourced from environment variables. The database driver checks the database first and falls back to the config value if no record exists, so config defaults still apply until you override them.
You can also mix both drivers in the same application by listing database-driven flags separately:
'flags' => [ 'comments' => env('TOGGLE_COMMENTS', true), 'related-articles' => env('TOGGLE_RELATED_ARTICLES', false),], 'database_flags' => [ 'breaking-news-banner', 'newsletter-signup-modal',],
Flags in database_flags always use the database driver (with config fallback), flags in flags stay read-only, and any unlisted flag falls back to the global driver. This lets stable flags stay in .env while operational flags can be flipped without a deploy.
Runtime Control and Caching
When the database driver is active, you can enable or disable flags at runtime. An editor publishing breaking news might flip a banner on from an admin screen without touching .env or shipping a deploy:
Toggle::enable('breaking-news-banner'); // Later, once the story is no longer front-pageToggle::disable('breaking-news-banner'); // Or remove the override entirely and fall back to the config defaultToggle::delete('breaking-news-banner'); Toggle::all(); // ['breaking-news-banner' => true, 'newsletter-signup-modal' => false, ...]
Lookups are cached, and you can tune the cache store and TTL through environment variables:
TOGGLE_CACHE_ENABLED=trueTOGGLE_CACHE_STORE=redisTOGGLE_CACHE_TTL=3600
The bundled Toggle Eloquent model clears the cache automatically when records are saved or deleted, and you can also flush it manually with Toggle::forgetCache('name') or Toggle::flushCache().
For undefined flags, the package's behaviour is configurable: return false, return true, or throw a ToggleNotFoundException so missing flags fail loudly during development.
TOGGLE_DEFAULT=falseTOGGLE_DEFAULT=trueTOGGLE_DEFAULT=exception
Inertia and Artisan
If your frontend uses Inertia, the included ShareTogglesWithInertia middleware exposes every toggle as a flags prop. This allows your Vue, React, or Svelte components to conditionally render features without requiring additional API calls:
use OffloadProject\Toggle\Middleware\ShareTogglesWithInertia; ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ ShareTogglesWithInertia::class, ]);})
const { flags } = usePage().props if (flags.breakingNewsBanner) { showBanner()}
The package also provides a handful of Artisan commands for day-to-day work, including toggle:list to inspect every defined flag and its current state, toggle:create to scaffold a new flag in config/toggle.php and .env (with optional --active and --db flags), and toggle:cache-clear to flush either all toggle caches or a single entry.
You can learn more and view the source code on GitHub.