laraarabdev/filament-gatekeeper
Filament Gatekeeper adds a streamlined authorization management layer to Filament panels, helping you organize and control access to resources and actions. It simplifies defining permissions and assigning them to users, so teams can manage who can do what directly from the admin UI.
Install the package and publish its assets/config:
composer require laraarabdev/filament-gatekeeper
php artisan vendor:publish --tag="filament-gatekeeper-config"
php artisan vendor:publish --tag="filament-gatekeeper-migrations"
php artisan migrate
Register the plugin on each panel you want guarded in your PanelProvider:
->plugin(GatekeeperPlugin::make())
Then run the discovery pass that writes permissions for your Filament resources:
php artisan gatekeeper:discover
Where to look first:
config/filament-gatekeeper.php — discovery toggles, naming/prefix conventions, cache settings, super-admin bypass.permissions / package table) — this is the source of truth the panel reads from.First use case: open a Resource, run discovery, and confirm two permission entries appear (one for the field, one for the column). Log in as a user without them and verify the field/column is hidden. That end-to-end loop (discover → assign → verify) is the whole package in miniature; everything else is scaling it up.
Auto-discovery as the default workflow. The package walks your Filament resources, extracts form fields and table columns, and derives permission names from them. Rather than hand-writing a policy per resource, you let discovery generate the keys and then attach them to roles. v1.1.5 deduplicates this discovery, so a field that appears in both a form schema and a table (or a column repeated across a resource and its relation manager) resolves to a single permission instead of duplicated entries — see the rewritten README for the full auto-discovery contract (what gets scanned, how names are built, how to opt out).
Discovery in the deploy pipeline. Treat gatekeeper:discover as a deploy step, not a one-off. A typical flow:
php artisan migrate --force
php artisan gatekeeper:discover --no-interaction
php artisan permission:cache-reset # or the package's cache command
php artisan optimize
Discovery is idempotent, so running it on every deploy keeps permissions in sync as resources evolve. Run it after migrations and before config/route caching so newly added fields exist when the panel boots.
Resource-scoped permission naming. Discovery derives names from the resource and the field/column key, which means renaming a field silently orphans its permission. If you need long-lived keys, set the field name explicitly and keep it stable:
TextInput::make('internal_notes') // permission key derived from 'internal_notes'
Where the package exposes a prefix/grouping option in the config, use it to namespace by panel or domain — this makes role assignment far more legible in the UI and keeps multi-panel installs from colliding.
Layering with policies. Discovery gives you the keys; policies give you the rules. Keep coarse access (viewAny, view, create, update, delete) in policies as usual, and use the package's field/column permissions for the finer-grained "can this user see this specific input" question. Don't duplicate the coarse checks in field permissions — you'll end up debugging two competing sources of truth.
Progressive hardening. Roll out per-resource, not globally. Enable discovery for one Resource group, verify, then widen via the config allow/deny list. This keeps the blast radius small when a naming convention doesn't match your expectations.
Relation managers and nested schemas. Resources with relation managers produce additional discovered entries. Verify a resource with at least one relation manager before assuming the flat case generalizes — nested schemas are the most common source of unexpected duplicates (and the area v1.1.5's dedup most directly improves).
gatekeeper:discover and re-check role assignments — a role pointing at a now-collapsed duplicate may end up with a permission that no longer exists.->when() conditionals, or loops over runtime data won't be seen as static schema components. If a field must be guarded, declare it statically and gate it with ->visible() instead.items.name and name are different keys.How can I help you explore Laravel packages today?