insenseanalytics/laravel-nova-permission
Nova tool for managing roles and permissions in Laravel Nova, built on spatie/laravel-permission. Adds permission and role resources to Nova with a familiar UI, letting admins create, assign, and organise access control without leaving the dashboard.
composer require spatie/laravel-permission insenseanalytics/laravel-nova-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
php artisan vendor:publish --tag=nova-permission-config
Skim config/nova-permission.php for the model/table mappings and guard settings before writing any code. Mismatches here are the #1 source of "roles don't show up".app/Providers/NovaServiceProvider.php:
public function tools()
{
return [
new \Insenseanalytics\LaravelNovaPermission\LaravelNovaPermission,
];
}
use Insenseanalytics\LaravelNovaPermission\RoleSelect;
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Name'),
RoleSelect::make('Roles', 'roles')
->options(Role::where('guard_name', 'web')->pluck('name', 'id')),
];
}
editor role, tick a few permissions, then assign it to yourself via the RoleSelect field on your user detail page and confirm a $user->can('...') gate returns true. If that loop works end-to-end, the package is correctly installed.Where to look when things misbehave: the tool's config file, then the package's src/ (resources are typically plain Nova resources registered by the tool, so you can trace the field/property names), then Spatie's permission.php.
Bootstrap permissions in code, manage them in the UI. The tool is great for granting roles to individual users and tweaking permission sets, but treat roles/permissions themselves as seed data so environments stay reproducible:
// database/seeders/PermissionSeeder.php
foreach (['posts.view', 'posts.create', 'posts.publish'] as $name) {
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
}
$editor = Role::firstOrCreate(['name' => 'editor', 'guard_name' => 'web']);
$editor->syncPermissions(['posts.view', 'posts.create']);
The UI-created permissions will be silently absent from staging/prod until you add them to the seeder — this is the single most common drift problem with permission UIs.
Keep the RoleSelect field guarded. Don't let non-admins edit roles just because the field is on the form:
RoleSelect::make('Roles')
->canSee(fn (Request $request) => $request->user()->hasRole('admin'))
->options(Role::where('guard_name', 'web')->pluck('name', 'id')),
Align gates with Nova visibility. Rather than duplicating permission logic, drive Nova's canSee / authorize from the same Spatie permissions so the UI and the app agree:
public function authorize(Request $request)
{
return $request->user()->hasPermissionTo('roles.manage');
}
Add a Gate::before super-admin bypass in AuthServiceProvider so admins don't need every permission explicitly.
Multi-guard setups need explicit options. If you have web and api guards, filter RoleSelect options by guard and make sure the user model has Spatie's HasRoles trait with the correct $guard_name. The tool's config lets you point at custom Role/Permission models — if you subclass them, update the config and re-register any resources the tool expects.
Cache invalidation as a habit. Spatie caches the permission map. After any UI change that should propagate to long-running processes (Horizon workers, etc.), run:
php artisan permission:cache-reset
or forget the spatie.permission.cache key in your deployment step.
Authorize the tool, not just the resources. Restrict the tool itself in NovaServiceProvider::tools() with ->canSee(...) so operators without permission-management rights never see the sidebar entry.
vyuldashev/nova-permission, which has kept pace.GuardDoesNotMatch when you assign a web role to an api user. In a mixed-guard app this is common and the tool will surface it as a 500 on save. Make the guard a first-class config decision and filter RoleSelect options by guard_name.config/nova-permission.php exists and the models/tables keys match your actual schema (especially if you renamed tables via Spatie's config).Role model declared in one but not the other produces confusing empty dropdowns.DB::transaction around scripted role changes.RoleSelect and role badges on a Nova index will lazy-load roles per row. Add $user->load('roles') or a with('roles') in the resource's indexQuery if the table gets slow.nova-components/ is often cheaper than patching a stale dependency.php artisan tinker and inspect $user->getRoleNames() and $user->getAllPermissions(). That distinguishes "role not actually attached" (guard or cache) from "gate not wired" (policy/Gate::before), which are the two failure modes the UI masks equally well.How can I help you explore Laravel packages today?