rupadana/filament-shield
Filament Shield adds role and permission management to Filament panels, wrapping spatie/laravel-permission with a ready-made UI, policy generation, and per-resource permission checks so you can control who sees and does what.
Filament Shield wires spatie/laravel-permission into Filament panels so that every Resource, Page and Widget gets a matching permission (e.g. view_any_user, create_user, page_Settings) and a generated Policy. You almost never write permission strings by hand — you create Filament components and let Shield derive them.
Minimum sequence on a fresh app:
composer require rupadana/filament-shield
php artisan shield:install # publishes config + migration, wires the plugin, offers --fresh/--tenant
php artisan shield:super-admin --user=1
Then register the plugin on your panel (->plugin(FilamentShieldPlugin::make())) and run the generator:
php artisan shield:generate --all --panel=admin
--all covers resources, pages, widgets and custom permissions in one pass; without a selector the command is a no-op and you will wonder why nothing appeared.
Where to look first
config/filament-shield.php — the source of truth for permission naming, discovery and the generated Role resource.app/Policies/ — the generated policies; these are yours to edit.App\Models\User — must implement FilamentUser and use HasRoles; Shield's super-admin bypass is a Gate::before hook, not a trait.First real use case: a resource that only some users may touch. Create the Filament resource, run shield:generate --all, seed/assign the resulting permissions to a role in the Role resource UI (or via seeder), then confirm $user->can('update_post') flips the table's edit button on and off. That round-trip — resource → generate → assign → verify — is the loop you will run dozens of times per project.
3.0.5 is a patch on the 3.x line with no published changelog detail beyond the diff; there are no migration steps, config key renames or command signature changes to action. Treat it as a drop-in upgrade and keep using the 3.x workflow above.
Generate on every structural change. The reliable habit is: add/rename/remove a Resource, Page or Widget → php artisan shield:generate --all → php artisan permission:cache-reset. Committing the generated policies keeps diffs reviewable; regenerating blindly in CI will silently drop hand edits unless policies.merge is enabled.
Scope the generator per component. On large apps --all is slow and noisy. Target one thing at a time:
php artisan shield:generate --resource=PostResource --panel=admin
php artisan shield:generate --page=Settings --panel=admin
php artisan shield:generate --option=policies_and_permissions
Keep policies thin, permission-aware. Generated policies are plain Laravel policies; put real rules in them and let Shield's permission names be the first line of defence:
public function update(User $user, Post $post): bool
{
return $user->can('update_post') && $post->author_id === $user->id;
}
Super admin without sprinkling checks. Configure super_admin.define_via_gate => true and let the Gate::before hook short-circuit. Do not add if ($user->hasRole('super_admin')) to policies — you will duplicate the bypass and make tests non-deterministic.
Seed roles, not clicks. For repeatable deploys, use Shield's seeder to snapshot the permission matrix into a ShieldSeeder/RoleSeeder and run it before any UserSeeder. Ordering matters: assigning a role in a user seeder before roles exist will fail silently in some drivers.
Multi-panel. Each panel has its own context; pass --panel= and publish the Role resource per panel (shield:publish --panel=admin) if admins and tenants need separate navigation. Permissions accumulate across panels, so keep naming consistent via permissions.separator and permissions.case and set them before the first generation.
Tenancy. With tenant_model configured, install with --tenant so the Role resource is tenant-scoped and policies receive the team context. Verify with a second tenant early — cross-tenant leakage here is the classic failure mode.
Custom permissions and non-Filament surfaces. Add bespoke permissions in the Role resource UI (or the custom_permissions config) for things with no Filament component — API endpoints, queued jobs, artisan actions — and check them with the same $user->can(...) calls. For APIs, point auth_provider.guard at your Sanctum guard so the same matrix applies.
Blade and non-panel code just use the standard directives: @can('view_any_post'). No Shield-specific syntax is needed once permissions exist.
permission:cache-reset or optimize:clear. Stale cache produces "works locally, 403 in staging" reports.permissions.case and separator are permanent-ish. Changing camel/upper/lower or the separator after permissions exist in the DB creates a parallel set of orphaned permissions. Decide on day one; if you must change it, regenerate and prune old rows.page_* permissions, but the component still needs public static function canAccess() / canView() wired to them, otherwise the permission is decorative.Gate::before bypasses everything, so actingAs($superAdmin) can mask missing permissions. Write at least one test as a plain role-holding user, and consider disabling super_admin.enabled in the test config.--all vs. explicit options. Mixing flags can double-generate and overwrite customized policies. Prefer one flag per invocation and keep generated policies under version control so accidental overwrites are visible.permission.table), is it on the role, is the cache cleared, does the policy method actually return true, is super_admin.intercept_gate interfering. That order resolves nearly every report.shield_resource (swap or subclass the Role resource), policies.path + merge, and the per-type except/generate lists in config. Reach for those before editing files inside vendor/.How can I help you explore Laravel packages today?