fullstackllc/novaspatiepermissions
NovaSpatiePermissions integrates Spatie's Laravel Permission package with Laravel Nova, providing ready-made Nova resources and fields to manage roles, permissions, and model assignments directly from your admin panel. Streamline access control without writing custom Nova tooling.
Install alongside its two hard dependencies. This package is a Nova tool, not a standalone authorization system — it wraps spatie/laravel-permission and needs Nova installed to do anything.
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
composer require fullstackllc/novaspatiepermissions
Register the tool in app/Providers/NovaServiceProvider.php:
public function tools()
{
return [
new \FullstackLLC\NovaSpatiePermissions\NovaSpatiePermissions(), // verify exact namespace from the package's src/
];
}
Look here first, in this order:
src/ directory — confirm the tool class name, whether it registers Nova resources (Role/Permission) or a custom tool route, and whether it ships a Role field for user forms.config/permission.php — guard names, table names, and cache.expiration_time. Almost every "why doesn't this work" question traces back here.NovaServiceProvider::tools() / resources() — you need to know what the package auto-registers so you don't double-register a Role resource of your own.First real use case: open Nova as an admin → find the Roles screen the tool adds → create editor, attach posts.update and posts.publish permissions → go to a User detail page → assign the editor role → log in as that user and confirm $user->can('posts.update') returns true. If that loop works end-to-end, the integration is correct.
Reality check on adoption: this package shows zero dependents and no stars. That doesn't make it bad, but it means you are the QA team. Budget an hour to read the source (it's small) before committing, and pin the version in
composer.json. Compare againstvyuldashev/nova-permissionor building a 60-line Role resource yourself — for many teams the DIY route is cheaper than debugging an unmaintained wrapper.
Roles on the User resource. Most teams don't want users editing permissions directly; they want a role picker. Put a BelongsToMany (or the package's own field, if it ships one) on app/Nova/User.php:
use Laravel\Nova\Fields\BelongsToMany;
public function fields(Request $request)
{
return [
// ...
BelongsToMany::make('Roles', 'roles', \Spatie\Permission\Models\Role::class)
->searchable(),
];
}
Alternatively, if the package provides a Role field, prefer it — it handles multi-select + guard filtering for you. Check src/Fields/ before hand-rolling.
Keep seeding authoritative, use the UI for exceptions. The failure mode of permission UIs is drift: someone clicks a checkbox in production, nobody records it, and php artisan migrate:fresh on staging produces a different permission set. Treat a seeder as the source of truth and the Nova tool as an escape hatch:
// database/seeders/RolesAndPermissionsSeeder.php
foreach (['posts.view','posts.create','posts.update','posts.delete'] as $p) {
Permission::findOrCreate($p, 'web');
}
$editor = Role::findOrCreate('editor', 'web');
$editor->syncPermissions(['posts.view','posts.create','posts.update']);
Role::findOrCreate('admin', 'web')->syncPermissions(Permission::all());
app(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions();
Run the seeder in CI on every deploy — it's idempotent because of findOrCreate.
Wire the checks where developers actually live. The UI only stores grants; enforcement is Spatie's job.
// Controller / policy
if (! $request->user()->hasPermissionTo('posts.publish')) {
abort(403);
}
// Blade — requires no extra setup once roles exist
@can('posts.publish')
<button>Publish</button>
@endcan
// Policy
public function update(User $user, Post $post): bool
{
return $user->hasAnyPermission(['posts.update', 'posts.manage-all']);
}
Gate the tool itself. If the tool is visible to every Nova user, you've handed your permission model to whoever can log in. Add canSee / canRun (or the tool's authorize callback) checking for a super-admin role:
public function tools()
{
return [
(new \FullstackLLC\NovaSpatiePermissions\NovaSpatiePermissions())
->canSee(fn ($request) => $request->user()?->hasRole('super-admin')),
];
}
Extend the package's models rather than editing vendor code. Spatie lets you swap models in config/permission.php ('models' => ['role' => App\Models\Role::class, ...]). If you need a description, label, or tenant_id column, subclass and point the config at yours — then confirm the Nova tool still resolves correctly (it may type-hint Spatie\Permission\Models\Role directly, which is the classic breakage point).
Combine with Gate::before for super-admins so you never have to grant every permission explicitly:
// AuthServiceProvider::boot()
Gate::before(function ($user, $ability) {
return $user->hasRole('super-admin') ? true : null;
});
Returning null (not false) is essential — it lets normal policy checks continue.
The permission cache will bite you. Spatie caches the whole permission table. Mutations made through Eloquent in a separate process (a seeder, a tinker session, a queue worker) don't invalidate the cache in your web process. If a checkbox in Nova doesn't take effect until you restart, this is why:
php artisan permission:cache-reset
Make this part of your deploy script after migrate --force and after seeding. The Nova tool generally resets the cache for changes it makes through its own UI — but not for anything else, and not across processes.
Guard mismatch is the #1 silent failure. hasPermissionTo('posts.update') uses the default guard from config/auth.php. If your web guard isn't the same one the role/permission rows were created under, every check returns false and nothing errors. Verify with:
Role::first()->guard_name; // must match
Permission::first()->guard_name;
config('auth.defaults.guard');
If you run api + web, either create permissions per-guard explicitly (Permission::findOrCreate($name, 'api')) or accept that they're separate universes.
hasPermissionTo() throws. Wrap it or use can() when you only care about a boolean — hasPermissionTo raises PermissionDoesNotExist for typos, which is arguably a feature during development but a 500 in production.
Nova 4 vs 5 compatibility. This package predates recent Nova majors. Confirm the nova constraint in its composer.json and the ToolServiceProvider/Nova::script API it uses. If it targets Nova 3, it may load but render a blank tool panel. Test the actual UI, not just composer require.
Eager-load roles on list views. Nova's index queries hydrate every user; without $user->load('roles') or with('roles') you'll see an N+1 that grows with user count. Nova's BelongsToMany field handles this, but a hand-rolled Text::make('Roles', fn ($u) => $u->roles->pluck('name')->join(', ')) will not.
Don't delete a role you're still assigning. The pivot tables have foreign keys; the UI may let you remove a role while users still reference it depending on FK constraints. Prefer "deactivate" flags over deletes if roles are part of your business vocabulary.
Wildcards aren't native. Spatie has no posts.* matching. If your permission list is exploding, either implement a Gate::before that expands wildcards, or model coarser permissions (posts.manage). Don't assume the Nova UI's checkbox grid will scale past a few hundred permissions — it won't be pleasant.
Teams / multi-tenancy. If you enable Spatie's teams feature, the schema changes (team_id on the pivot tables) and most third-party Nova permission tools break or silently ignore the team scope. Read the package source for any team_id handling; assume it's absent until proven otherwise.
Extension points worth knowing: the tool's canSee/canRun, the config-driven model swap, and Nova's own Resource::authorizable() / fieldsForIndex. If the package fights you on any of these, the fallback is a hand-written Role resource with a BelongsToMany permission field — roughly 80 lines and fully under your control.
How can I help you explore Laravel packages today?