bbs-lab/nova-permission
bbs-lab/nova-permission adds a Laravel Nova UI for spatie/laravel-permission, letting you create roles and permissions, assign them to users, and manage access control directly from Nova without custom admin screens.
Architecture fit. This package is a thin but opinionated authorization layer that sits between Laravel Nova and spatie/laravel-permission, extending Spatie's model from global (role-level) permissions to per-instance ("granular") permissions. Architecturally it is a natural fit for any Nova-administered Laravel app that already uses Spatie — it fills a real gap (Nova has no first-class UI for scoped authorization) without introducing a new permission engine. The v2.0.0 release is best understood as a stability and correctness release that formalizes 1.x's implicit contracts into hard requirements, not a feature release. The feature set is unchanged; what changes is that the previously aspirational parts now actually work.
Integration feasibility. Feasible for a greenfield or clean-upgrade path; materially harder for an existing 1.x install. Three integration surfaces carry the weight:
BBSLab\NovaPermission\Traits\HasRoles (not Spatie's) and implement CanOverridePermission. Any policy calling hasPermissionToOnModel() is coupled to the package trait, so this is a cross-cutting change touching every authorization call site, not a package swap in isolation.php artisan migrate. This is convenient but removes an explicit opt-in step — you can no longer reason about schema changes from your own migration history alone. Only Spatie's migration remains publishable (--tag="permission-migrations").Policy methods now return ?bool while keeping the Model $model parameter. Overrides must not narrow the signature — a silent source of fatal errors during upgrade if any project policy was written against 1.x.Technical risk. The dominant risks are upgrade-time, not runtime:
unique(name, guard_name) with unique(name, guard_name, authorizable_id, authorizable_type) will fail on any existing dataset containing duplicate (name, guard_name) rows — which is exactly the state 1.x encouraged, because scoped permissions couldn't be created. Existing installs may hold duplicate-named permissions with null authorizable_id that only differed by intent. This needs a data audit before migrating. The MySQL-safe explicit index name mitigates the 64-character identifier limit but signals the constraint is real.gate_cache key is gone; caching is opt-in via config('nova-permission.cache.enabled') (default false, env NOVA_PERMISSION_CACHE / NOVA_PERMISSION_CACHE_TTL). Correct-by-default, but it means a silent performance regression for any deployment that was relying on the old gate cache and doesn't notice the config key changed. This should be treated as a required post-deploy config change, not an optional one.uses Spatie's HasRoles directly will need review.^8.2 but the CI matrix is PHP 8.3–8.5. The floor is nominally supported but not tested. If any environment runs 8.2, flag it.Key questions.
(name, guard_name) permission rows in production, and do we have a remediation script if so?HasRoles trait directly, or type-hinting against Spatie's policy/permission classes?Policy methods with narrowed signatures or non-nullable bool returns?gate_cache enabled in our config? If so, who owns turning on NOVA_PERMISSION_CACHE and validating TTL/invalidation behavior?Stack fit. Requires PHP ^8.2, Laravel Nova ^4.0 || ^5.0, and spatie/laravel-permission ^6. The Spatie 6 constraint is the sharpest edge: if we are on Spatie 5, this release forces a Spatie major upgrade concurrently, which brings its own migration and cache-key changes. Sequence these as two distinct changes, not one.
Migration path. Treat this as a coordinated schema + auth-contract upgrade, not a composer bump:
(name, guard_name) permissions and for any permission rows with non-null authorizable_id/authorizable_type created under 1.x (which, given the 1.x defects, likely do not exist or are malformed). Decide on a merge/rename strategy before running the composite-index migration.Policy base method signature.NOVA_PERMISSION_CACHE and TTL, verify per-model authorization-cache invalidation (called out as fixed) with a role change → permission check round-trip.Compatibility. The explicit ?bool return type on base policy methods is the compatibility boundary for project code; the Model $model parameter retention is the other. Both are static, greppable, and fixable — good properties. The trait swap is the least greppable: HasRoles is a common import name and collisions may look identical at a glance. Use a static analysis pass rather than code review.
Sequencing. Spatie 6 → package trait/contract adoption → policy signature fixes → migrations (staging first) → cache enablement. Do not combine the Spatie major with the package major in a single PR; if something breaks, you want to know which.
Maintenance. Genuinely improved. Full Pest suite at 100% line coverage, mutation testing, architecture tests, PHPStan level 8, Pint, a GitHub Actions matrix (Nova 4 & 5 × Laravel 11–13 × PHP 8.3–8.5), and Dependabot. For a package operating in the authorization path of an admin panel, this is the difference between "trust the README" and "trust the build." The mutation-testing and architecture-test signals are meaningful — they suggest the maintainer intends to prevent the class of silent breakage that killed Nova 4 support in 1.x.
Support. Mixed but net positive. Documentation was rewritten and now includes a Requirements section, a granular-permissions guide, a caching page, and a Testing page, plus a workbench that doubles as a demo (seeded admin/writer/reader roles, Post and Service resources showing general vs. granular permissions, Nova impersonation). That materially lowers ramp-up. The counterweight is a single-maintainer, small-namespace package (BBSLab) now owning the authorization trait — that is a real bus-factor consideration for something in the auth path.
Scaling. Opt-in caching is the right default for correctness but puts the burden on us to reason about permission volume. Granular permissions multiply the permission-check surface: per-instance checks on list views are the classic N+1 authorization problem. The fixed per-model authorization-cache invalidation is therefore load-bearing, not incidental. Before enabling granular permissions broadly, we should load-test a Nova index with realistic permission cardinality and confirm cache invalidation doesn't thrash on role edits.
Failure modes. Ranked:
gate_cache key removed, new key off by default; config carried over from 1.x does nothing and nobody notices until the admin panel is slow.Ramp-up. Low for a developer familiar with Spatie and Nova: the mental model is unchanged, the workbench is a working reference, and the docs are rewritten. Budget realistic effort for the upgrade itself — the trait/contract adoption, policy signature sweep, and data audit are a day or two on a small app and more where policies are numerous. Add explicit onboarding for the caching model, since "off by default and centralized" is easy to misread as "we don't need to think about it."
TPM recommendation. Adopt v2.0.0, but as a planned change with a data-audit gate, not a routine bump. The release's core value proposition — granular permissions that actually work, verified by tests — is real and was previously unavailable despite being advertised in 1.x. Do not enable granular permissions in production until the composite-index migration and cache-invalidation behavior have both been validated against a production-shaped dataset.
How can I help you explore Laravel packages today?