coringawc/filament-acl
Role and permission management for Filament panels. coringawc/filament-acl adds ACL resources to create roles, assign permissions, and control access to your admin resources, pages, and widgets, integrating with Laravel's authorization layer for straightforward access control.
Architecture fit. coringawc/filament-acl is a trait-first authorization layer for Filament v5 panels. Its architecture is deliberately narrow: it treats the Filament owner (Resource, RelationManager, Page, Widget, or a free-form custom permission) as the permission subject, discovers opted-in owners, generates permission keys, syncs them into a Spatie permission store, and ships a built-in role/permission management resource. Consumption is via opt-in traits — HasResourcePermissions, HasRelationManagerPermissions, HasPagePermissions, HasWidgetPermissions — with optional method overrides (getPermissionSubject(), shouldRegisterPermissions(), getSharedPermissionOwner(), getPermissionActions(), getPermissionCustomActions(), getPermissionPanel()) rather than required base classes or static properties. That is a good fit for an existing Filament application: adoption is additive and per-owner, not a wholesale rebase onto package-provided base classes. The package is explicitly decoupled from filament-shield (no Composer dependency, no runtime calls, no config contract assuming Shield's presence), which removes the most common source of coupling risk in this category and means it can coexist with a Shield-based panel during a transition. It also keeps domain policy logic out of scope, which is the right boundary — it means the package does not compete with our application policies, it feeds them.
Integration feasibility. Feasible with a well-understood envelope. Hard constraints are Spatie ^8.2, PHP ^8.3, and Filament ^5.0 — these are floors, not tested ceilings, and the release notes state plainly that Laravel 13 and PHP 8.5 were not tested. Two integration surfaces require deliberate app-side work rather than being purely drop-in:
ChecksPermission, with the extra PermissionAction|string|null $permissionAction argument kept last in the signature (viewAny(mixed $user, PermissionAction|string|null $permissionAction = null), update(mixed $user, Model $record, PermissionAction|string|null $permissionAction = null)). Every policied model must be touched to thread that argument through, and the guidance is explicit that policies must not infer the owner from the request or route when it can be passed explicitly. This is mechanical but repo-wide.#[PermissionSubject], #[PermissionActions], #[CustomPermissionActions], and #[RegisterPermissions(false)] are not inherited. Any resource inheritance hierarchy in our codebase must redeclare attributes or override methods on each child. This is the single most likely source of silent misconfiguration — a subclass will appear correctly wired and simply not carry its parent's permission metadata.Custom actions stay native (auth()->user()?->can('archive', [$record, PostResource::class]), ->authorize('archive', PostResource::class)), and the package deliberately avoids shipping a custom Filament action class. That is a genuine feasibility positive: no new UI primitive to learn, review, or maintain.
Technical risk. Rated low-to-moderate, and the risks are mostly process rather than runtime:
require_explicit_opt_in = true by default, and an owner returning shouldRegisterPermissions(): false must not be synced, must not appear in the permission UI, and must have package-level checks skipped. A regression in that third clause is a silent permission bypass, and the AGENTS.md flags it as "essential. Do not regress it" — meaning it has been a live regression risk.getSharedPermissionOwner()) inherit permissions and should normally disappear from discovery and the permission UI in favor of the canonical owner. Panel scope is configurable separately for roles and permissions, and the AGENTS.md requires changes to propagate consistently across sync commands, built-in resource queries, hidden-role helpers, permission lookup helpers, and protected-role assignment. Five layers that must move together is a coordination hazard we will inherit as operators, particularly if we configure different panels.Gate::before(), protected from edit/delete by RolePolicy, assignable via an admin-user command. A Gate::before() bypass is exactly the kind of thing that must be reviewed by someone who understands the blast radius before it is enabled in production.Key questions. For the maintainer: (1) Is Laravel 13 / PHP 8.5 support on a roadmap, and what is the compatibility policy — does the package track Filament majors on release, on a lag, or reactively? (2) What is the tested ceiling for Spatie 8.x, and is a Spatie 9 migration anticipated? (3) How is the shouldRegisterPermissions(): false skip-check invariant covered by tests — is there an explicit adversarial test asserting no package-level check runs for opted-out owners? (4) Is the protected-role Gate::before() bypass covered by a test asserting it cannot be enabled for a role that is not hidden/configured? (5) What is the intended behavior when a shared owner points at a class that is itself opted out? For us: (6) Which panel(s) do we scope roles versus permissions to, and do we intend to manage another panel via getManagedPermissionPanel()? (7) How many resource subclasses inherit Filament owners today, and who owns redeclaring attributes on each? (8) Do we have a policy that forbids agent tooling — the two Laravel Boost skills ship in the validation matrix and become content we carry but did not author or test.
Stack fit. This package is a clean fit only if our declared baseline matches its tested envelope: PHP 8.3+, Filament 5, Spatie ^8.2. If we are on Filament 4 or earlier, below PHP 8.3, or an older Spatie major, the declared constraints rule us out and the conversation ends. Assuming we qualify, the fit is architecturally conservative — traits instead of base classes, native Laravel policies, native Filament authorization, no Shield coupling — which means it composes with our existing code rather than displacing it, and it can be evaluated behind a flag on a single panel before any broad commitment.
Migration path. Adoption is incremental by design, and that is the main reason to prefer it over a bespoke build. The sequence I would run:
filament-acl.integration.require_explicit_opt_in remains true — do not relax the opt-in default to make onboarding feel faster; the explicit opt-in is the safety property.HasResourcePermissions, run the sync command, and confirm the generated permission keys appear in the built-in role/permission resource. This validates discovery, key generation, and store writes in one step.ChecksPermission pattern with the extra $permissionAction argument last, and write a negative test asserting denial when the permission is absent. Policy changes, not package installation, are the bulk of the effort.shouldRegisterPermissions(): false and assert, with a test, that it is absent from sync, absent from the permission UI, and that package-level checks are skipped. This is the invariant most worth locking down early because regressions here are silent.getSharedPermissionOwner() and any non-default getPermissionPanel() / getManagedPermissionPanel() configuration. Because panel scope for roles and permissions is configured separately and must stay consistent across sync commands, resource queries, hidden-role helpers, lookup helpers, and protected-role assignment, this is the stage to re-run the full sync and diff the resulting permission set rather than assume convergence.Gate::before() bypass is enabled, and get that decision reviewed. Verify it is hidden from normal selects and package tables and that RolePolicy blocks edit/delete.can('archive', [$record, PostResource::class])) only once the resource-driven permissions are stable, so that any authorization surprise is attributable to one change.Compatibility. Two hard gates and one soft gate. Hard: PHP 8.3+ and Filament 5. Hard: Spatie ^8.2. Soft and unproven: Laravel 13 and PHP 8.5 — treat these as unsupported until demonstrated, and keep this package off the critical path of any Laravel 13 upgrade plan. Also confirm the attribute non-inheritance rule against our resource hierarchy before rollout; if we have deep Filament inheritance, budget the redeclaration work explicitly rather than discovering it in review.
Sequencing. Land the dependency bump as a routine change — it is behavior-preserving, so it does not need its own regression cycle. Then run the pilot → policy → opt-out → broaden → shared-owner/panel-scope → protected-role sequence above as a timeboxed spike, with the go/no-go decision at the end of step 4. If the opt-out invariant holds under test and the policy thread-through is as mechanical as it appears, the remaining steps are execution, not risk. If either is messier than expected, we still have the option to stop at one panel with no architectural debt — which is the advantage a trait-first package buys us.
Maintenance. The maintainer's posture is the strongest signal in this package's favor and the main reason maintenance burden on us is likely low. All 12 third-party GitHub Actions are pinned to verified commit SHAs; Dependabot monitors Composer and Actions weekly with an explicit three-day version-update cooldown while security updates remain immediate; CI runs Pest, PHPStan, Pint, actionlint, SHA/config verification, and distributed Boost skills across eight jobs. Patch releases are behavior-preserving with stable public APIs — v2.0.1 changed zero runtime behavior, which is exactly the property we need from a long-lived pinned dependency. The residual maintenance risk is upstream compatibility drift, not neglect: the package must track Filament majors and Spatie majors, and there is no published commitment on latency. We should carry an owned watchlist item for Laravel 13 / PHP 8.5 rather than assume the maintainer's timeline matches ours.
Support. Two considerations. First, 0 stars and no evident third-party adoption means our engineers are effectively the first line of support; we cannot assume Stack Overflow coverage or a body of community answers, so the AGENTS.md and the shipped Boost skills become primary documentation — which is a real asset but also content we did not author and must validate ourselves. Second, if our policy forbids agent tooling, we should confirm the Boost skills are inert (installed but unused) rather than assume they can be excluded cleanly from the shipped surface, since they are part of the validated matrix.
Scaling. Permission sync is a batch operation over discovered owners, so cost scales with the number of opted-in Filament owners and the size of the permission set, not with user count — this is a low-scaling-risk component. Two scaling dimensions do need attention: (1) panel count — panel scope is configured separately for roles and permissions, so as we add panels, the configuration matrix grows combinatorially and each new panel is a chance to break the five-layer consistency requirement; (2) permission-key cardinality — free-form custom permissions plus per-owner action keys can inflate the permission table, so I would avoid using custom permissions as a general-purpose feature-flag mechanism. The Gate::before() protected-role bypass is constant-time and not a scaling concern, but it is a correctness concern at any scale.
Failure modes. Ranked by severity, because this is an authorization surface:
shouldRegisterPermissions(): false still receiving package-level checks, or worse, being silently treated as permitted. Highest severity, silent, and explicitly flagged upstream as a do-not-regress invariant.getSharedPermissionOwner() points at an opted-out class.Gate::before() granting more than intended because it was enabled without reviewing its blast radius.Ramp-up. Modest for anyone fluent in Filament and Laravel authorization, with one non-obvious learning curve: the mental model shift to treating the Filament owner as the permission subject. That reframing is what makes the traits, the shared-owner semantics, and the panel-scope configuration coherent, and it is not the mental model most Laravel developers arrive with. I would budget a half-day of reading (AGENTS.md first, then policies, then the built-in resource) plus the pilot spike in Integration step 2–4, and expect one engineer to own the whole surface — policies, sync, panel scope, protected role — rather than distributing it, precisely because the five-layer consistency requirement means partial ownership is how inconsistencies get introduced. The bundled Boost skills are a genuine ramp-up accelerant for teams already doing agent-assisted Laravel work and a non-event for teams that are not.
How can I help you explore Laravel packages today?