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

Toggle Laravel Package

mrshanebarron/toggle

mrshanebarron/toggle is a lightweight Laravel/PHP package for adding simple toggle behavior to your app—switch features, settings, or states on and off with minimal setup. Useful for lightweight feature flags and runtime controls.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish the config

    composer require mrshanebarron/toggle
    php artisan vendor:publish --tag=sb-config
    

    The sb- prefix is not cosmetic — v1.0.1 was a fix release specifically for config file naming. The published file lands at config/sb-toggle.php. If --tag=sb-config reports "no publishable resources," run php artisan vendor:publish interactively or read the provider's publishes() array; don't guess at tag variants beyond that.

  2. Where to look first, in this order

    • config/sb-toggle.php — for this package the config file is the feature registry. Every toggle name you can check lives here.
    • The service provider's mergeConfigFrom() call — tells you the default config key the package resolves to before publishing. That key is the real name to use in config() calls.
    • The facade/helper class — confirms the exact method names (active, enabled, isActive…). The docs describe "a clean helper or facade API" without pinning the signature, so read the class rather than inferring from the README.
  3. Define your first toggle

    // config/sb-toggle.php
    return [
        'toggles' => [
            'new-checkout' => env('FEATURE_NEW_CHECKOUT', false),
            'beta-dashboard' => env('FEATURE_BETA_DASHBOARD', false),
        ],
    ];
    

    Note the env() default of false; see the boolean-coercion gotcha below before you rely on it.

  4. First use case — gate one code path

    if (toggle('new-checkout')) {
        return $this->newCheckout($request);
    }
    
    return $this->legacyCheckout($request);
    

    Resolve via the facade or helper rather than instantiating anything: the container-bound checker picks up your published config, and it's the seam you'll swap in tests.

  5. Confirm the values actually loaded before debugging anything else

    php artisan config:show sb-toggle
    

    A toggle that "doesn't work" is almost always a flag that isn't where you think it is, not a broken check.

Implementation Patterns

Single source of truth for flag names. Toggle names are bare strings, so a typo doesn't throw — it falls through to a default. Centralise them:

final class Feature
{
    public const NEW_CHECKOUT = 'new-checkout';
    public const BETA_DASHBOARD = 'beta-dashboard';

    public static function all(): array
    {
        return [self::NEW_CHECKOUT, self::BETA_DASHBOARD];
    }
}

Then toggle(Feature::NEW_CHECKOUT). You can also assert in a test that every constant exists as a key in config('sb-toggle.toggles'), which turns silent typos into red builds.

Resolve flags at the edge, not in the domain. Check the toggle once in the controller/middleware/composer and pass a boolean inward. Domain services that call toggle() internally become impossible to test without touching global config and are painful to delete once the flag ships.

public function store(StoreOrderRequest $request, CheckoutService $checkout)
{
    return $checkout->handle($request->validated(), feature: toggle(Feature::NEW_CHECKOUT));
}

Route-level gating for whole features (an entire beta dashboard, an admin tool):

Route::middleware(['web', 'can:access-beta'])->group(function () {
    // ...
});

Put the toggle() call inside a small gate/middleware rather than repeating it per route — when the flag is removed you delete one class instead of twelve route definitions.

Blade usage: wrap markup, not decisions. Toggles in views are fine for showing/hiding UI, but the authorisation-equivalent decision should already have been made upstream. Otherwise the controller assumes the feature is on while the view renders the old UI, and you get half-migrated pages.

@toggle('new-checkout')
    <x-checkout-v2 :cart="$cart" />
@else
    <x-checkout-v1 :cart="$cart" />
@endtoggle

Gradual rollout. This package is config/ENV driven with no database, which means a flag flips only on deploy or config change — there is no runtime kill switch and no per-user targeting. If you need percentage rollouts, compose it yourself and keep the package as the on/off master switch:

function rollout(string $flag, ?int $userId = null, int $percent = 0): bool
{
    if (! toggle($flag)) {
        return false;
    }

    if ($percent >= 100) {
        return true;
    }

    $userId ??= auth()->id() ?? 0;

    return (crc32($flag . ':' . $userId) % 100) < $percent;
}

Hash on something stable (user ID), never on rand() — otherwise a user sees the new checkout on page one and the old one on page two. Put the percentage in config next to the flag so it moves through the same deploy path.

Environment layering. Flags differ per environment by design: on in staging, off in production, on for one canary. Drive that from .env only — FEATURE_NEW_CHECKOUT=true — and keep the config file committed and identical everywhere. Never edit config/sb-toggle.php per server.

Testing pattern. Override config per test; never touch .env or set real environment variables.

protected function setUp(): void
{
    parent::setUp();
    config(['sb-toggle.toggles.new-checkout' => false]);
}

public function test_legacy_checkout_used_when_flag_off(): void
{
    config(['sb-toggle.toggles.new-checkout' => false]);
    // assert v1 behaviour
}

public function test_new_checkout_used_when_flag_on(): void
{
    config(['sb-toggle.toggles.new-checkout' => true]);
    // assert v2 behaviour
}

Because the package is a pure config read, there's no fake to install and nothing to mock — which is its main strength. If the facade is macroable or the checker is bound to a contract, bind a double in a shared test case instead, so a future switch to a DB-backed driver doesn't ripple through every test.

Flag lifecycle discipline. Every toggle is debt with a removal date. Keep them in one config file, name them after the behaviour (new-checkout) not the ticket (PROJ-1423), and record the intended removal in the config comment. A feature-flag package without a cleanup habit becomes a permanent pile of dead branches — this is the single biggest long-term risk with a package this thin.

Deployment order.

php artisan config:cache
php artisan queue:restart
php artisan view:clear   # only after a package upgrade that changes directives

queue:restart matters here more than usual: long-running workers hold the cached config from before the deploy, so a flag flipped today won't take effect in queued jobs until they restart.

Gotchas and Tips

v1.0.1 renamed the config file to the sb- prefix — this is the release to watch. Anything referencing the old unprefixed name now silently returns null. After upgrading:

rm config/toggle.php            # or whatever the old published name was
php artisan vendor:publish --tag=sb-config --force
grep -rn "config('toggle" app/ config/ resources/   # rewrite to sb-toggle

A null from a renamed config key does not throw. Your toggle() call returns the default, which is false, which means the feature quietly never turns on. The failure mode is silence, so grep rather than test.

A config filename change in a patch release is a red flag. Pin to ^1.0.1, roll to staging first, and read the changelog for key renames that weren't called out. For a 0-star package, treat patch releases as potentially breaking until proven otherwise.

env() booleans are strings. env('FEATURE_NEW_CHECKOUT', false) returns the string "false" if someone writes FEATURE_NEW_CHECKOUT=false in .env, and "false" is truthy in PHP. Cast explicitly:

'new-checkout' => filter_var(env('FEATURE_NEW_CHECKOUT', false), FILTER_VALIDATE_BOOL),

This is the most likely way a toggle gets stuck permanently on in production, and it looks exactly like a package bug.

config:cache is mandatory after every config change. In production config() reads the cached array. Editing config/sb-toggle.php on the server without re-caching has literally zero effect. Combine that with the boolean-string issue above and you have the two most common 30-minute debugging detours.

env() outside config files returns null. Calling env('FEATURE_X') in a controller or Blade template returns null once the config is cached. Flags must be read through config().

mergeConfigFrom merges shallowly. If your published file defines toggles as an array, it replaces the package's default array entirely rather than merging key by key. Only relevant if future versions ship default toggles — but if they do, your local file will silently drop them.

Unknown toggle names fail closed and silent. toggle('typo-name') returns the default (false) with no warning. Mitigate with the constants-class test above, or a debug-only assertion in a wrapper:

function feature(string $name): bool
{
    if (app()->hasDebugModeEnabled() && ! array_key_exists($name, config('sb-toggle.toggles', []))) {
        throw new InvalidArgumentException("Unknown feature toggle [{$name}]");
    }

    return toggle($name);
}

No database means no runtime kill switch. You cannot turn a broken feature off without a deploy or an SSH session plus config:cache. For anything genuinely risky, pair the config flag with your own runtime check (a cache-stored override, a short-TTL "enabled" key) so you can disable it in seconds rather than minutes.

Blade directive caching. If a package upgrade changes or renames a directive, the compiled views in storage/framework/views still contain the old code. php artisan view:clear after any upgrade that touches the Blade surface.

Facade resolution failures. "A facade root has not been set" means the binding key changed or the provider isn't discovered — typically a stale bootstrap/cache/services.php, not a code problem. php artisan optimize:clear clears it.

Debugging checklist, in order:

  1. php artisan config:show sb-toggle — are the values right, and are booleans real booleans?
  2. Is the config cached, and does the cached copy predate your change? (bootstrap/cache/config.php)
  3. php artisan about / bootstrap/cache/packages.php — is the provider auto-discovered?
  4. php artisan vendor:publish --list — does the sb-config tag still exist after the upgrade?
  5. Only then instrument the toggle() call itself.

Extension points before forking. If you need DB-backed flags or user targeting, look for a contract bound in the container you can rebind, or a manager class you can extend(). Wrapping the package behind your own Feature service from day one costs an hour now and makes that swap a one-file change later; the thin API is exactly the kind of thing worth wrapping early.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource