liberusoftware/feature-flags-api
liberusoftware/feature-flags-api is a Laravel/PHP package for exposing and managing feature flags through an API. Use it to define flags, check enabled state, and toggle features for controlled rollouts or environment-specific behavior.
This is a thin presentation adapter — it is the HTTP/API layer that exposes feature-flag functionality owned by another module (likely liberusoftware/module-feature-flags or similar). Treat it as a transport package, not a business-logic package. That distinction drives everything below.
1. Install and inspect before you code
composer require liberusoftware/feature-flags-api
Because this package has 0 stars, 0 dependents, and a NOASSERTION license, do not assume published docs exist. Your first 10 minutes should be spent reading the source:
| Look at | Why |
|---|---|
composer.json |
Real namespace, required PHP/Laravel versions, sibling liberusoftware/* dependencies (these reveal the domain package) |
src/Providers/*ServiceProvider.php |
Route registration, config merge, published assets, middleware groups |
routes/ (or src/Routes/) |
The actual endpoint surface, prefixes, and middleware |
src/Http/Controllers/ |
Which methods exist — this is your API contract |
src/Http/Resources/ |
Response shape; this is what your frontend will bind to |
config/feature-flags.php (if present) |
Driver, cache TTL, route prefix, auth guard |
2. Register / discover
If the package is not auto-discovered, add the provider manually in bootstrap/providers.php (Laravel 11+) or config/app.php. Then:
php artisan vendor:publish --provider="LiberuSoftware\FeatureFlagsApi\FeatureFlagsApiServiceProvider" --tag=config
php artisan route:list --path=feature # confirm the actual prefix
3. First use case: evaluate a flag over HTTP
The canonical day-one workflow is a frontend asking "is this feature on for this user?" Typical shape:
curl -H "Authorization: Bearer $TOKEN" \
https://app.test/api/feature-flags/checkout-v2
{ "data": { "key": "checkout-v2", "enabled": true, "variant": "treatment-b" } }
Wire this into your SPA bootstrap (one call returning all active flags) rather than per-component calls — see Gotchas.
4. Sanity test before building on it
Write one Pest/PHPUnit feature test hitting the endpoint through $this->actingAs($user)->getJson(...). If that passes, the package is wired correctly and you can start integrating. If it doesn't, the failure is almost always (a) provider not registered, (b) route middleware rejecting you, or (c) the domain package missing.
Since this is a presentation adapter, resist adding logic to its controllers. Instead, register your own routes alongside it, or decorate the domain service it resolves. If the package binds FeatureFlagEvaluator::class to an interface, rebind it in your own provider:
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->bind(
\LiberuSoftware\FeatureFlagsApi\Contracts\FeatureFlagResolver::class,
\App\FeatureFlags\CachedResolver::class
);
}
This lets you add request-scoped caching or audit logging without touching vendor code — critical when a package has zero community pressure to fix bugs.
The most common real-world integration: one authenticated call at app boot returns the resolved flag set, then the frontend never calls the flag API again for the session.
// app/Http/Controllers/Api/FlagBootstrapController.php
public function __invoke(Request $request): JsonResponse
{
$flags = FeatureFlag::query()
->where('active', true)
->get()
->mapWithKeys(fn ($f) => [
$f->key => [
'enabled' => $f->isEnabledFor($request->user()),
'variant' => $f->variantFor($request->user()),
],
]);
return response()->json(['data' => $flags])
->header('Cache-Control', 'private, max-age=60');
}
If the package already exposes a GET /feature-flags collection, prefer it and add an ?only=names filter via a policy or a thin custom controller rather than duplicating.
Feature-flag writes (create/enable/rollout percentage) should never be reachable by ordinary tokens. Wrap the package's write endpoints behind a gate:
Gate::define('manage-feature-flags', fn (User $u) => $u->hasRole('admin'));
Route::middleware(['auth:sanctum', 'can:manage-feature-flags'])
->group(base_path('vendor/liberusoftware/feature-flags-api/routes/api.php'));
Then in your AuthServiceProvider, or better, a small FeatureFlagPolicy so per-flag granularity (e.g. team-scoped flags) is possible later.
If the package ships FeatureFlagResource, do not return raw Eloquent models at call sites — always go through the resource. This is your one stable surface when the vendor bumps the adapter:
return FeatureFlagResource::collection($flags); // stable JSON:API-ish envelope
Pin the response shape in your own contract tests so a vendor update that renames enabled → is_active fails CI, not production.
Feature flags and test suites interact badly. Establish a helper:
// tests/Pest.php
function withFlags(array $flags): void
{
foreach ($flags as $key => $value) {
FeatureFlag::factory()->create(['key' => $key, 'active' => $value]);
}
Cache::tags('feature-flags')->flush();
}
Then each test that asserts on flag-driven behavior declares its flags explicitly. Never rely on the seeded production flag set in tests.
Because there is no changelog you can trust, wrap every call to the package's routes in a single frontend client module:
// resources/js/feature-flags.ts
export async function fetchFlags(): Promise<Record<string, FlagState>> { ... }
One file to patch when the adapter changes; the rest of the app is untouched.
Licensing is unresolved. NOASSERTION on a package you embed in a commercial product is a legal risk. Check LICENSE in the repo; if absent, treat the package as "all rights reserved" and get written permission or vendor the relevant routes under your own namespace. Do not ship it to a client without resolving this.
Zero dependents means zero battle-testing. Expect the adapter's middleware stack to assume its host application's auth guard. In practice you will almost always need to override config('feature-flags-api.middleware') to swap in auth:sanctum or auth:api. Do this in your own config/feature-flags-api.php, not by editing vendor files.
Route prefix collisions. The package likely registers api/feature-flags/.... If your app also owns a FeatureFlag model and resource routes, run php artisan route:list early and grep for duplicates. Overlapping route names silently shadow each other.
Cache invalidation is the #1 production bug. Flag evaluations are usually cached; toggling a flag via the API does not automatically bust the cache of other processes. Check whether the adapter dispatches a FeatureFlagUpdated event or fires a cache-forget. If not, add an observer:
FeatureFlag::updated(fn ($flag) => Cache::tags('feature-flags')->flush());
and use tag-aware cache stores (Redis/Memcached). Avoid the file and array drivers for flag caching in multi-node setups.
N+1 on the collection endpoint. If the adapter eager-loads nothing, GET /feature-flags returning 40 flags can produce 40+ queries as each resource touches ->isEnabledFor(). Publish/modify the resource or override the controller to ->with(['segments', 'rules']).
Debug the response, not the controller. When a flag returns the wrong value, trace: (1) the raw DB row, (2) the domain resolver's output, (3) the resource transformation. Nine times out of ten the adapter is fine and the flag's targeting rules are the culprit — the adapter is a dumb serializer.
Version pinning is non-negotiable. With no release cadence you can trust (Last release: 2026-10-26 is unusually far ahead — verify this is a real tag and not a mis-tagged dev branch), pin exactly:
"liberusoftware/feature-flags-api": "1.2.3"
and audit with composer outdated --direct on your own schedule. Never ^1.0 on a package with no semantic-release discipline.
Extension points to check first: interface bindings in the service provider, events dispatched on flag mutation, and any FeatureFlagApi::macro() hooks. These are the only legitimate customization seams. Anything else means you should fork into app/FeatureFlags/ and drop the dependency.
Observability. Add a middleware that logs {user_id, flag_key, result, ms} for the check endpoint. Flags silently change product behavior — having a queryable log of what the server actually resolved saves hours when a PM insists a flag "was definitely on."
Fallback strategy for missing keys. Decide up front whether an unknown flag key 404s or defaults to false. Most teams want the adapter to 404 in dev (typo detection) and default-false in production. Implement the toggle in a middleware so behavior is consistent across all flag endpoints rather than patched per controller.
How can I help you explore Laravel packages today?