z3d0x/filament-simple-permissions
Filament Simple Permissions adds straightforward role and permission management to Filament admin panels. It provides ready-to-use resources and helpers for assigning roles and permissions and restricting access to pages and resources, keeping ACL setup simple for Laravel apps.
Start by confirming the package even fits your Filament version. It was last released in June 2023 and the repository is archived, so check composer.json constraints against your Filament major before you plan anything around it. If you're on Filament 3+, run composer require z3d0x/filament-simple-permissions in a throwaway branch and let the solver tell you the truth. If it won't resolve, stop here — the rest of this assessment is only useful to you as a design reference.
Assuming it installs:
spatie/laravel-permission. Publish and run Spatie's migrations first, then publish the package config with php artisan vendor:publish --tag="filament-simple-permissions-config".PanelProvider::panel() and nowhere else.php artisan filament:optimize-clear then php artisan permission:cache-reset. Open the new Roles entry in the navigation.admin role, tick the permissions you already gate with, assign it to your own user, and walk every page of the panel confirming you can still reach all of it. This is the cheapest possible proof that guard alignment is correct, and guard misalignment is the failure mode you are most likely to hit.When something is off, look in this order: the package config → Spatie's config/permission.php (guard names and table names must agree) → the package's RoleResource → your own policies. That order resolves the large majority of problems.
Treat permissions as data with one owner. Keep a single source of truth — a PermissionSeeder, an enum, or a config array — and have the package's resource present exactly that set. Scattering 'edit_posts' string literals across policies, Blade, and Filament resource canX() methods is how you accumulate orphaned permissions nobody can safely delete from the UI.
The role screen is administration; policies are enforcement. The UI is a convenience for humans assigning roles. Actual authorization belongs in Laravel policies and Gate calls, and Filament's own authorization flow (canViewAny, canCreate, canDelete, etc.) should be backed by those policies. Don't let the existence of a permissions UI tempt you into thinking the UI is the security boundary.
Guard alignment is the pattern that matters most. Filament panels can define their own auth guard. Decide once whether roles attach to web, to the panel guard, or to both — then set it in the package config and in permission.php and never mix. The classic symptom of getting this wrong is a role that exists, an assignment that exists, and a user who still sees 403s everywhere.
Seed idempotently and deploy through code. Role creation and permission sync belong in a seeder using firstOrCreate plus syncPermissions, run after migrate as part of deploy. Never rely on a human clicking through the Roles screen in production — it's unrepeatable, unauditable, and someone will do it in staging and forget production.
A thin policy per model. The shape that ages best is a small policy mapping each ability to a permission name (view_any_post → canViewAny), plus a narrow Gate::before for a super-admin role. Anything more elaborate tends to duplicate what Filament already asks the policy.
Multi-panel setups: register selectively. Register the plugin only on the panel where role administration belongs. Otherwise every panel — including the customer-facing one — grows a role editor, which is both confusing and a privilege-escalation surface if the wrong person reaches it.
Extend by subclass, not by editing vendor. Adjust label, navigation group, and sort via config. If you genuinely need a new column or action, subclass RoleResource and register your class in place of the default, keeping the file under app/Filament. Edits under vendor/ vanish on the next composer update.
Feature tests: seed the permissions, assert on the ability. Use RefreshDatabase plus your permission seeder, then assert with $user->can('...') rather than driving the UI. For each panel page, cover at least one negative case — a user without the permission gets a 403 — because deny-by-default is the property you actually care about.
php artisan permission:cache-reset (or call forgetCachedPermissions()) in both tests and deploy scripts. A freshly granted permission that "doesn't work" is nearly always this.config:cache freezes your permission list. Any permission definitions you keep in a config file are baked at cache time. Either re-cache as an explicit deploy step or move the definitions into the database where the seeder can sync them.guard_name on both the role rows and the permission rows against the guard your panel authenticates with.model_has_roles before Spatie's migration has run fails with an opaque SQL error that reads like a package bug.Gate::before. Returning true from Gate::before short-circuits every policy check, including ones you deliberately locked down. Scope it to a dedicated super-admin role or flag, and write a test proving a non-super-admin is still blocked.config:cache + archived package = upgrade risk. Because the repository is archived, treat compatibility as your responsibility. Keep composer.lock committed, pin the version rather than floating, and budget time to fork if a future Filament major leaves you stranded. The codebase is small enough that owning a fork is realistic — verify that before you adopt, not after.How can I help you explore Laravel packages today?