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

Unleash Bundle Laravel Package

stogon/unleash-bundle

Symfony bundle for integrating the Unleash feature flag service into your application. Define and manage feature toggles, run gradual rollouts, and target users with flexible activation strategies through a simple, configurable API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin ^1.0.1. Earlier 1.0.0 builds shipped a broken container binding for FeatureRepository (an invalid named argument $httpClient) and injected the wrong HttpClientInterface implementation into UnleashHttpClient. On 1.0.0 the package can fail at container resolution time — typically the first time you resolve the repository or hit a feature check — so treat 1.0.1 as the real "first usable release" rather than an optional patch.

composer require <vendor>/laravel-unleash:^1.0.1
php artisan vendor:publish --tag=unleash-config

Then set the three values you actually need in .env:

UNLEASH_URL=https://unleash.example.com/api/
UNLEASH_API_TOKEN=default:development.xxxxxxxx
UNLEASH_ENVIRONMENT=local

Where to look first:

  1. config/unleash.php — confirm the environment name, the API URL (it needs the trailing /api/), and whether caching is enabled. Most "it doesn't work" reports are a bad URL or a token scoped to the wrong environment.
  2. The package's service provider bindings — this is the code that was wrong in 1.0.0. If you are extending or decorating UnleashHttpClient or FeatureRepository, read the bindings before writing your own, and rebind after the package provider registers.
  3. php artisan about / php artisan tinker to confirm the config is actually loaded (config('unleash.environment')).

First use case — gate a single route:

if (Unleash::isEnabled('new-checkout')) {
    return view('checkout.v2');
}

return view('checkout.v1');

Verify it end-to-end by toggling the flag in the Unleash UI and reloading. If the value never changes, you are almost certainly looking at a cached response rather than a broken client — see the gotchas below.

Implementation Patterns

Wrap the facade, don't scatter it. Raw Unleash::isEnabled('string-literal') calls are untyped and unsearchable. A thin domain wrapper pays for itself almost immediately:

final class Features
{
    public const NEW_CHECKOUT = 'new-checkout';
    public const AI_SUGGESTIONS = 'ai-suggestions';

    public static function checkout(): bool
    {
        return Unleash::isEnabled(self::NEW_CHECKOUT);
    }
}

This gives you one place to add context, logging, or a kill switch, and it makes flag cleanup a grep instead of an archaeology project.

Use context for stickiness, not for targeting logic you own. Pass user/session/tenant context so Unleash can make the rollout decision, rather than reimplementing percentage math:

Unleash::isEnabled('ai-suggestions', [
    'userId' => (string) $user->id,
    'sessionId' => session()->getId(),
    'environment' => app()->environment(),
    'properties' => ['plan' => $user->plan, 'region' => $user->region],
]);

Blade directives for view-layer flags. Keep presentation branches out of controllers:

@feature('new-checkout')
    @include('checkout.v2')
@else
    @include('checkout.v1')
@endfeature

Middleware for whole route groups. Guarding a group of routes is a better fit than checking inside every controller action — the flag becomes a deployment concern rather than a business rule. Register it in bootstrap/app.php (Laravel 11+) or Kernel.php, and be deliberate about ordering: put it after auth so user context is available, and before anything that triggers side effects.

Testing: never hit the network. The package resolves its client through the container, which is exactly the seam you want. In TestCase::setUp or a dedicated trait, swap the binding for a stub repository or a fake HTTP client so tests are deterministic and fast:

$this->app->bind(FeatureRepository::class, fn () => new InMemoryFeatureRepository([
    'new-checkout' => true,
]));

If your tests are flaky or slow, the first thing to check is whether they are making real HTTP calls to Unleash — a container binding that bypasses the injected HttpClientInterface would defeat this, which is precisely the class of bug 1.0.1 fixed.

Background jobs and long-running workers. Feature state is read at check time. In a queue worker or Octane process that lives for hours, you will hold whatever the cache holds. If a flag must take effect "now" for a running worker, either shorten the TTL, resolve the repository fresh per job, or re-check inside the job rather than capturing the boolean at dispatch time.

Deployment pattern. Ship both code paths behind the flag, deploy with the flag off, enable for internal users, ramp, then remove the dead branch and the flag together. The flag is scaffolding — schedule its removal in the same PR that adds the winning path.

Gotchas and Tips

This release is a container-binding fix, so treat DI as the sharp edge. Two specific hazards:

  • Do not redeclare the package's bindings with named arguments unless you mirror the constructor parameter names exactly. 1.0.0's $httpClient mismatch is the canonical example of how a rename in a constructor silently breaks a service definition that looked fine. If you publish or override the provider, prefer positional closures or fn (Container $app) => new FeatureRepository($app->make(HttpClientInterface::class)) over named-argument arrays.
  • If you swapped HttpClientInterface for your own implementation (Guzzle wrapper, retrying client, mock), re-verify it after upgrading. 1.0.1 corrected which implementation gets injected into UnleashHttpClient; if you were relying on 1.0.0's accidental behavior, your custom client may now be bypassed.

Config caching bites hard. php artisan config:cache bakes UNLEASH_* values in. Changing .env without re-running config:cache gives you the old URL/token — and a confusing 401 or DNS error that looks like a package bug. Always php artisan config:clear when debugging connectivity.

Caching means stale flags. If the package caches responses, a flag you just toggled may not appear for the TTL window. Don't file a bug until you've waited out the TTL or flushed the cache. In local development, disable caching in config/unleash.php so toggles are instant.

Decide your failure mode on purpose. When Unleash is unreachable, you get whichever default the client chooses. For a kill switch guarding a database migration, "fail open" is a production incident; for a cosmetic UI experiment, "fail open" is correct. Don't let the package's default decide this for you — wrap calls where the blast radius is asymmetric, and log the fallback so you notice the outage.

Environment names must match exactly. A token issued for production used with UNLEASH_ENVIRONMENT=prod returns no flags, not an error. This is the second most common cause of "all my flags are off."

Check the URL shape. The base URL needs the /api/ path segment. Truncated URLs return HTML error pages that fail JSON decoding, surfacing as an opaque exception rather than a 404.

Token hygiene. Unleash API tokens are environment-scoped and often read-only; keep them in .env (never in config/unleash.php defaults) and rotate them like any other secret. Client-side tokens are intended for browser use — don't expose a server token through an endpoint that returns flag state.

Extension points. The FeatureRepository and HttpClientInterface bindings are the intended seams for custom caching, retry/backoff, or test doubles. Rebind them in a provider that registers after the package provider. Anything else — reaching into internals, extending the client class — will break on the next patch release, as 1.0.1 demonstrates.

Log the decisions, not just the errors. A single debug line emitted when a flag resolves is invaluable six months later when someone asks why a feature appeared for one tenant and not another. Keep it behind a config toggle so it can be switched on in production when you actually need it.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json