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

Filament Feature Flags Laravel Package

ryangjchandler/filament-feature-flags

Filament Feature Flags adds a simple feature flag system to Filament panels, letting you toggle features on and off per user or globally using Laravel's Pennant. Define flags, control access with policies, and conditionally show resources, pages, and widgets.

View on GitHub
Deep Wiki
Context7

Getting Started

This plugin is a Filament panel page that reads and writes Laravel Pennant feature flags, so you need both pieces before anything shows up.

  1. Install Pennant and create its storage table — the plugin has nothing to toggle without it:
    composer require laravel/pennant
    php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
    php artisan migrate
    
  2. Install the plugin and publish its config:
    composer require ryangjchandler/filament-feature-flags
    php artisan vendor:publish --tag="filament-feature-flags-config"
    
  3. Register it on the panel you want the UI in (usually your admin panel, not a tenant-facing one):
    use RyanChandler\FilamentFeatureFlags\FeatureFlagsPlugin;
    
    public function panel(Panel $panel): Panel
    {
        return $panel->plugin(FeatureFlagsPlugin::make());
    }
    
  4. Look first at the published config file. That's where features (and the scopes you expose in the UI) are declared. Confirm the exact array keys against the file you get — the plugin's config schema is the thing most likely to differ between versions.
  5. First use case: declare one feature in config, load the panel, flip it on for the current user, then verify from Tinker:
    Feature::active('my-feature'); // true
    

Implementation Patterns

Define features in one place, reference them by stable string key. The key you put in config is the Pennant key written to the features table. Treat it like a database column name — never rename it casually, or you orphan activation rows and silently reset behaviour.

Separate "global" flags from "scoped" flags. Pennant's default scope is the authenticated user, but most real deployments need one of:

  • Global — on/off for everyone; resolve with a null/explicit scope and check in a service provider or middleware.
  • Per-model — Feature::for($user)->active('beta-dashboard'); expose the User (and Team, if you're multi-tenant) as a scope in the plugin so admins can toggle individuals.
  • Per-tenant — in a tenancy setup, make the tenant the scope and check Feature::for(tenant())->active(...); don't rely on the session-resolved user inside tenant context.

Guard at the edges, not deep in the domain. The cleanest pattern is a middleware alias or a Blade directive that reads the flag once and either aborts or swaps a component:

// app/Http/Middleware/EnsureFeatureActive.php
if (! Feature::active($feature)) {
    abort(404);
}

Then ->middleware('feature:billing-v2') on the route group. Keeping checks at the boundary means deleting the flag later is a one-line change per route.

Put defaults in code, overrides in the UI. Set the intended default (true/false) in config or a seeder so a fresh environment behaves predictably, and let the admin panel only ever express deliberate overrides. This is what saves you when someone wipes the features table in staging.

Scope-aware seeding for demos and tests. Rather than clicking the UI, drive state programmatically:

Feature::for($user)->activate('new-checkout');
Feature::for($user)->deactivate('new-checkout');
Feature::forget(); // drop Pennant's in-request cache

Wire it into your deploy checklist. Because the panel writes to the database, feature state is data, not config. It does not travel with your code, so staging→production promotion needs an explicit decision about which flags carry over.

Gotchas and Tips

Pennant caches resolved values in memory for the request. If you toggle a flag mid-request (or in a test right after the app has already resolved it), you'll get the stale value. Call Feature::forget() — or Feature::purge() for a full reset — after any programmatic change. This is the single most common source of "the button doesn't work" confusion.

Nothing renders without the migrations. If the panel loads but toggles don't persist, or the page 500s, check that Pennant's features table exists in that connection. In multi-database setups the plugin will happily write to the wrong connection.

An undefined feature resolves to false, not an error. Feature::active('typo-here') fails silently. Cross-check config keys against Feature::active(...) calls in your codebase — a rename on one side and not the other is invisible until a user complains.

Config caching in production. If feature definitions live in the plugin config, php artisan config:cache during deploy will freeze them until the next cache clear. Make sure your deploy script runs it, and remember that any config change requires a re-cache.

Lock down the panel page. The plugin registers a Filament page; it inherits whatever your panel's access gate is. If your admin panel is reachable by more than a handful of people, add an explicit canAccess() / policy check — feature toggles are effectively production switches and shouldn't be available to every panel user by default.

Zero dependents and no star history means you are the QA team. Pin the version in composer.json, read the source once (it's small), and don't upgrade casually across Filament majors. Filament v3→v4 plugin APIs and page registration have both moved before.

Have an escape hatch. If the package stalls, the UI is thin: you can replace it with a custom Filament page over Pennant in an afternoon. Keep your feature names in config/constants rather than hardcoded strings in views so the swap is mechanical.

Treat flags as debt with an expiry. Add the flag name to the PR that introduces it and open a follow-up to remove the branch once it hits 100%. Panels like this make flags easy to create and easy to forget — the graveyard of permanently-on flags is the real failure mode, not the plugin itself.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource