composer.json for Laravel/Nova constraints. If you are on Nova 4/5 and Laravel 10/11, test in a branch first.spatie/laravel-permission, not a replacement:
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
composer require graphene-ict/nova-permissions
php artisan vendor:publish --provider="GrapheneICT\NovaPermissions\..."
php artisan nova:publish
php artisan optimize:clear
src/ for the Nova tool/resource classes. In your app, check app/Providers/NovaServiceProvider.php for tools() / resources(). Confirm guard names in config/auth.php and config/permission.php.admin role in Nova, attach permission names that match your policies/gates, assign it to a test user, then verify both normal Laravel authorization and Nova resource authorization:
$user->can('posts.publish');
$user->hasRole('admin');
Keep permission names in code, assign roles in Nova. Treat Spatie as the source of truth and this package as the admin UI. Define permission names as constants/enums and seed them so typos do not become production bugs.
// database/seeders/RolesAndPermissionsSeeder.php
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
Permission::findOrCreate('posts.view', 'web');
Permission::findOrCreate('posts.publish', 'web');
$admin = Role::findOrCreate('admin', 'web');
$admin->syncPermissions(['posts.view', 'posts.publish']);
Wire permissions into policies and Nova authorization. Use $user->can() in policies, then make Nova resources respect the same checks via authorizedToViewAny, authorizedToUpdate, canSee, and canRun.
public function publish(User $user, Post $post): bool
{
return $user->can('posts.publish');
}
Use Nova for day-to-day role assignment, not schema management. Let operations staff assign roles to users through Nova. Keep permission definitions and baseline roles in seeders so deployments stay reproducible.
Clear Spatie’s permission cache after writes. UI changes may not affect can() until the cache is reset:
app(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions();
Or run php artisan permission:cache-reset in deploy scripts.
Be explicit about guards. Spatie roles/permissions are guard-specific. If Nova uses a different guard than your app, create permissions for the correct guard or unify guards deliberately. A permission created under the wrong guard will silently fail $user->can().
Handle teams/tenancy deliberately. If Spatie teams are enabled, roles are scoped by team context. Global role assignment will not automatically respect tenant boundaries. Test the Nova UI against a non-default team before relying on it.
Add auditability around role changes. Use Nova actions or model events to log who changed roles/permissions. This is especially important when non-developers can edit access through Nova.
guard_name on roles and permissions, and compare it with config/auth.php. Nova’s guard can differ from your application’s web guard.$user->can() still returns the old result. Always reset Spatie’s cache after programmatic or UI changes.Gate::before super-admin bypass, then test with a non-super-admin account.php artisan migrate --pretend first and watch for duplicate table creation.nova:publish, tool visibility, and resource authorization in a staging environment.php artisan config:clear. Frontend asset changes usually require php artisan nova:publish.$user->getAllPermissions()->pluck('name'), Role::with('permissions')->get(), and the current guard. These checks usually reveal whether the problem is in Nova, Spatie, or your authorization setup.How can I help you explore Laravel packages today?