kiritokatklian/nova-permission
A Laravel Nova tool for managing roles and permissions with Spatie's laravel-permission package. It adds a clean Nova UI to create roles, assign permissions, and attach roles to users, simplifying authorization management in Nova admin panels.
The package is a Nova tool + resource set for spatie/laravel-permission. It gives you UI to create roles/permissions and fields to attach them to your models.
Install the dependency (the fork exists mainly to track newer Nova/spatie versions, so pin both). As of 5.3.0 the package now declares support for spatie/laravel-permission v8 — the first release in the 5.x line to do so:
composer require kiritokatklian/nova-permission:^5.3 spatie/laravel-permission:^8.0
Confirm in composer.json / composer why spatie/laravel-permission that the resolved constraint matches your Nova major. If you are still on spatie v6/v7, you can stay on a pre-5.3 package release — but the two must move together, and composer update will happily pick 5.3.0 the moment you bump spatie, so check what actually resolved rather than what you typed.
Ensure spatie/laravel-permission is migrated (if not already):
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
On the spatie v7 → v8 jump, re-publish and diff the config file rather than assuming your old one is still valid, then php artisan config:clear.
Register the tool in app/Providers/NovaServiceProvider.php:
public function tools(): array
{
return [
new \Kiritokatklian\NovaPermission\NovaPermissionTool(),
];
}
Expose the bundled resources (Nova auto-discovers app/Nova, not vendor resources):
protected function resources(): void
{
Nova::resources([
\Kiritokatklian\NovaPermission\Role::class,
\Kiritokatklian\NovaPermission\Permission::class,
]);
}
Where to look first: the tool class (NovaPermissionTool) for static config hooks, then the Role / Permission resources, then the field classes (RoleBooleanGroup, PermissionBooleanGroup, and the select variants). Read the source—it's small and the README lags behind the code.
First use case: log in as a super-admin, open the Permissions menu, create permissions (posts.view, posts.publish), create a role, tick permissions on it, then add RoleBooleanGroup::make('Roles') to your User resource and assign the role. That end-to-end loop is the whole point of the package.
Assign roles to users with the BooleanGroup field:
use Kiritokatklian\NovaPermission\RoleBooleanGroup;
RoleBooleanGroup::make('Roles')
->hideFromIndex()
->help('Roles drive what this user can do in the app.'),
Attach permissions to roles — the bundled Role resource already includes this, but for a custom Role resource:
PermissionBooleanGroup::make('Permissions')
->options(fn () => \Spatie\Permission\Models\Permission::pluck('name', 'id'))
->hideFromIndex(),
Extend rather than replace the bundled resources. Create App\Nova\Role extends \Kiritokatklian\NovaPermission\Role, add fields (description, tenant, color), then register your class instead of the vendor one. Same for Permission. This keeps upgrades cheap.
Use the select fields for filtering/reporting. RoleSelect / PermissionSelect are thin wrappers that populate options from the spatie tables—handy on User index filters or on unrelated resources that need to reference a role.
Gate visibility centrally:
// NovaServiceProvider::tools()
new \Kiritokatklian\NovaPermission\NovaPermissionTool()
and restrict via NovaPermissionTool::canSee(...) (or wrap in Gate::allows) so only admins see the menu entry.
Keep the UI as a convenience layer, not the source of truth. Seed canonical permissions and role definitions in a seeder:
foreach (['posts.view', 'posts.publish', 'users.manage'] as $name) {
Permission::findOrCreate($name, 'web');
}
$editor = Role::findOrCreate('editor', 'web');
$editor->syncPermissions(['posts.view', 'posts.publish']);
UI edits then layer on top for ad-hoc changes; code stays reproducible across environments.
Super-admin bypass belongs in AuthServiceProvider, not in the package:
Gate::before(fn ($user, $ability) => $user->hasRole('super-admin') ? true : null);
Upgrade spatie and this package as one unit. When moving to spatie v8, the sequence that works: bump both constraints in composer.json, composer update kiritokatklian/nova-permission spatie/laravel-permission, run any new spatie migrations, php artisan config:clear, then php artisan permission:cache-reset. Only after that do you re-check the Nova UI — that way a broken authorization check is attributable to one change, not two.
viewAny/update against policies for the Role and Permission resources. With no policy, the menu and resources are effectively invisible. Register policies (or a Gate::before) before you assume the install is broken.DB::table() updates, raw seeders, or Octane workers won't. When in doubt: php artisan permission:cache-reset, or app(PermissionRegistrar::class)->forgetCachedPermissions() after bulk writes. This bites hardest immediately after the v8 upgrade, when stale cached state from the old version is still in your worker/Redis cache.guard_name mismatch is the #1 silent failure. A role on guard web cannot be assigned to a user whose guard is api, and a permission created under the wrong guard simply won't match can(). Multi-guard apps should scope the resources (or add a guard_name field + filter) rather than mixing everything in one list. The guard default comes from spatie's config/permission.php, so any change to that config during a major upgrade silently re-homes permissions created afterwards.spatie/nova-permission. If you're migrating, update import paths (Spatie\NovaPermission\* → Kiritokatklian\NovaPermission\*) in resources, fields, and any class strings in config. Grep for the old namespace rather than trusting the IDE.NovaPermissionTool::resources([...]), policy/canSee hooks) instead of overriding service providers. The exact method names drift between releases—verify against the source for your pinned version.RefreshDatabase with a permission seeder; call app(PermissionRegistrar::class)->forgetCachedPermissions() in setUp() so authorization assertions don't inherit a stale cache between tests. Run the suite once against spatie v8 specifically before shipping the bump — a green suite on v7 proves nothing about v8.composer.json, and if you hit an odd authorization failure after a routine composer update, the first thing to check is which spatie major you actually resolved to.How can I help you explore Laravel packages today?