eminiarts/nova-permissions
A Laravel Nova tool for managing roles and permissions with a clean Nova UI. Create and edit roles and permissions, assign them to users, and integrate with spatie/laravel-permission for flexible access control.
Architecture fit. eminiarts/nova-permissions is a UI-only layer inside Laravel Nova that renders a grouped checkbox matrix over spatie/laravel-permission data. It ships no authorization engine, no middleware, no policies, and no persistence layer of its own — it reads and writes Spatie's roles/permissions relations. That is a favorable shape for us if, and only if, we have already committed to both Nova and Spatie independently. If we have not, this package is not a reason to adopt either; it is a convenience on top of a stack decision that has already been made. Architecturally the surface is tiny: essentially one field class (~150 lines) plus Nova resource definitions, which means the "integration" is closer to vendoring a snippet than onboarding a dependency.
Integration feasibility. Feasible with a caveat that dominates the whole evaluation: the newest release (1.0.6, 2019-09-09) advertises PHP 7.2 / Laravel 6 / Spatie v3 as its ceiling. There is no published support for Nova 4/5, PHP 8, or Laravel 9+. On a modern stack we should plan for a fork-first integration — copy the field into app/Nova/Fields/ with a link to the upstream tag — rather than a clean composer require. That changes the effort profile from "install and configure" to "read, port, test, own," which is still small in absolute terms but is not a dependency-management activity and should not be planned as one. Effort estimate to a working screen: 1–2 engineering days on a compatible stack; 3–5 days if we are porting the field to a newer Spatie major and validating guard handling and relation shape.
Technical risk. Four risks concentrate almost all of the exposure:
$role->permissions populating, and hasPermissionTo() still returning false because permissions landed on web while the check runs against api. This produces a screen that appears to work and an authorization layer that does not — a class of bug that reaches production because tests exercise the UI path, not the enforcement path.sync semantics (medium likelihood, high impact — delayed). If the field syncs the relation, any permission granted outside the matrix (seeder, other package, manual grant) is silently removed on the next Nova save. This surfaces weeks into production, not at integration.explode('.') on the permission name. users.view groups under "Users"; view users does not group at all. The resource.action convention becomes load-bearing across the entire application the moment we adopt this.The package is also not a security boundary. It shows and hides checkboxes; it does not stop a crafted request, does not log changes, does not enforce separation of duties, and does not scope by tenant. Any compliance narrative ("we can demonstrate controlled, auditable access changes") has to be built around it, not from it.
Key questions to resolve before committing:
sync is correct and must be documented. If the latter, we override the fill logic to merge and own that divergence forever.Stack fit. The package is a natural fit only on a Nova 3 / Laravel 6–8 / Spatie v3 stack. On Nova 4/5 or Spatie v5/6, fit is conditional on a port, and the port is small enough to be worth doing but large enough that it must be scheduled rather than assumed. Prerequisites that must be true before the package is touched: migrations run, HasRoles applied to User, and php artisan permission:cache-reset succeeds. Get Spatie healthy standalone first — debugging a UI layer over a broken authorization layer is how a one-day task becomes a one-week task.
Migration path. Sequence matters more than usual here because the naming convention is load-bearing and renaming permissions later is a breaking change across every policy, @can directive, and canSee/canRun callback. The recommended order:
resource.action and enforce it in the seeder. This is a prerequisite, not a follow-on. Renaming later invalidates gates and shifts group headings.firstOrCreate). This is what makes the matrix stable across deploys and prevents manual drift between environments.App\Nova\Role resource with the grouped field, registered in NovaServiceProvider::$resources. Leave Nova's built-in Role resource untouched — isolating the dependency is what makes future Nova upgrades survivable.RolePolicy gating create/update on an admin role or a manage roles permission, and exclude manage roles itself from the matrix — otherwise an admin can self-escalate through the UI. The field is not an enforcement point; the policy is.Compatibility. Two hard checks before we trust the matrix: (a) compare the repo's composer.json against our app's, and pin 1.0.6 exactly — no caret constraint on a package with no release cadence; (b) if we are on Spatie v5/6, verify each call the field makes (hasPermissionTo, syncPermissions, the permissions relation, the PermissionRegistrar cache-forget) against current signatures. Most are stable; guard handling and the permissions() return shape are not, and those are exactly the two that fail silently.
Sequencing recommendation. Do not bundle this with other Nova work. Land it as an isolated change behind a feature flag if the org requires it, with the seeder and policy merged first, the field second, and the fork documented in the same PR. Testing should assert saved state, not browser behavior: seed permissions, actingAs an admin, hit the resource endpoint, assert $role->hasPermissionTo('users.view') and that an unticked permission is absent. That single test covers roughly 90% of what breaks here and survives Nova upgrades.
Maintenance. Assume upstream is dead and plan accordingly. With zero dependents and a five-year release gap, composer require should be treated as a way to read the source, not as a long-term install. The field is ~150 lines — owning it outright removes the maintenance risk at the cost of a named owner and a documented divergence from upstream. The real maintenance burden is not the code; it is the permission taxonomy. Every new resource adds permission strings, every rename breaks gates, and the grouping is unforgiving of inconsistent naming. That burden exists with or without this package, but this package makes it visible and therefore somewhat more disciplined.
Support. The package adds a support surface for internal operators and trusted admins, which is the right audience — this is not a customer-facing surface and should not be sold as one. Internal ops get a grouped matrix that measurably reduces misconfiguration and the ticket volume that follows from it, which is a defensible support-cost lever. What it does not give support is change history: there is no audit log, so "who removed that permission and when" is unanswerable from this tool. If that question matters — and for anything touching access control it usually does — it is build work on top, not a feature of the package.
Scaling. Two ceilings, both worth naming now:
Failure modes. Ranked by how long they take to surface:
guard_name explicitly and tests that assert hasPermissionTo, not just relation contents.permission:cache-reset runs. Mitigation is putting the reset in the deploy script, and teaching the team that "the new permission doesn't appear in Nova" means clear the cache before touching code.sync dropping out-of-band grants — surfaces weeks into production, after someone grants a permission through a script or another package and it vanishes on the next Nova save. Decide and document the source-of-truth model up front.canSee, authorizedToView, or a missing manage-roles permission, not the package. Test as a real admin, not a factory user with no roles.Ramp-up. Low and unusually well-bounded: the field is one file and ten minutes of reading covers it end to end. The honest onboarding cost is not the package, it is the convention — a new engineer must learn that permission names are load-bearing, that the matrix may be the source of truth (or may merge, depending on our fill override), and that the UI is not a security boundary. That is a half-day of documentation and one architecture note, and it should be written in the same PR that vendors the field. Realistic ramp: one engineer productive on day one; team-level fluency after one permission-set change cycle.
Bottom line for the roadmap. This is a cheap internal admin convenience with a known expiration date. It buys a delegated-administration screen for roughly one to two days of vendor-and-test work against two to four weeks to build the equivalent, and it can credibly pull "let admins manage their own users' access" forward a quarter — conditional on the fork cost being acceptable and on tenant scoping, audit logging, and enforcement being funded separately. It does not make us enterprise-ready and does not close a compliance control. Adopt it as a UI component with a named owner and a vendored copy, or decline it and spend the sprint elsewhere; what we should not do is install it as a dependency and assume someone else is maintaining it.
How can I help you explore Laravel packages today?