ferdiunal/nova-shield
Nova Shield is a Laravel Nova package for managing roles and permissions. It lets you control access to Nova resources, actions, and tools through a simple interface, making authorization easier to configure and maintain.
Before you install anything, check the version matrix. This package sits between two private/versioned dependencies (Laravel Nova and, almost certainly, spatie/laravel-permission), and with ~11 stars and zero dependents you are effectively the QA team. Read composer.json on the repo first and confirm the nova and spatie/laravel-permission constraints match what you actually run — Nova 4 vs Nova 5 is the usual break point.
composer require ferdiunal/nova-shield
# Publish whatever the provider exposes (config, migrations, lang)
php artisan vendor:publish --provider="Ferdiunal\NovaShield\NovaShieldServiceProvider"
php artisan migrate
Then look at src/NovaShieldServiceProvider.php (or src/Providers/) and src/ root before touching the UI. The three things you need to confirm from source, because they aren't documented reliably:
Role / Permission Nova resources auto-register or must be added to NovaServiceProvider::resources().roles / permissions / model_has_roles migrations.web, nova, or config-driven).First use case: don't try to model your whole authorization scheme on day one. Install it, create a single super-admin role, assign it to one user, and verify that a permission check gated through Nova actually flips. That end-to-end loop (Nova UI → DB → gate → UI) is the thing you're validating, not the CRUD screens.
A quick smoke test:
// tinker
$user = App\Models\User::first();
$user->assignRole('super-admin');
$user->can('viewAny', App\Models\Post::class); // with a Gate::before or policy using permissions
If the package ships a canSee/authorizable integration, this is also where you confirm Nova's own authorization hook is wiring permissions in rather than just showing you a table.
Treat it as an admin UI over Spatie, not as an authorization framework. The durable value is the Nova screens for editing roles and attaching permissions; everything downstream (policies, gates, middleware) should still be plain Laravel + Spatie API calls. That way the package is replaceable.
Seed roles and permissions in code, edit assignments in Nova. Permissions should be generated from a canonical list, not clicked in by hand — otherwise production and staging drift within a week.
// database/seeders/PermissionSeeder.php
foreach (['view', 'create', 'update', 'delete'] as $action) {
foreach (['Post', 'User', 'Invoice'] as $model) {
Permission::findOrCreate("{$action} {$model}", 'web');
}
}
$admin = Role::findOrCreate('admin', 'web');
$admin->syncPermissions(Permission::where('guard_name', 'web')->get());
Run this in every deploy before anything that reads permissions, and make it idempotent (findOrCreate + syncPermissions, never create).
Bootstrap super-admins outside the permission tables. A common pattern that survives the package being swapped out:
// AuthServiceProvider::boot()
Gate::before(function ($user, $ability) {
return $user->hasRole('super-admin') ? true : null; // null, not false
});
Returning null (not false) is essential — it lets normal policies still run for everyone else.
Gate Nova resource access on permissions, not roles. Roles rot; permissions are what you actually reason about.
public static function authorizedToViewAny(Request $request): bool
{
return $request->user()?->can('view User') ?? false;
}
Use a single guard consistently. If Nova authenticates via the web guard (the default), set config/auth.php guards and the Spatie guard_name to match. If you have a separate nova guard, every role and permission row needs guard_name = 'nova', and your policies must check that guard explicitly. Mixed guards is the number-one source of "the permission is there but can() returns false."
For multi-tenant or team-scoped setups: Spatie supports teams via config/permission.php (teams => true), but this package's UI almost certainly writes to the global tables without a team context. If you need teams, verify against source before committing — and be prepared to extend the resource or add a team_id filter yourself via Nova's indexQuery.
Cache aggressively, invalidate deliberately. Spatie caches the permission map. Any role/permission write performed outside of Eloquent events (raw SQL imports, DB::table updates) will leave stale cache until you run:
php artisan permission:cache-reset # or Cache::forget(config('permission.cache.key'))
The Nova UI goes through Eloquent, so it's fine — your seeders and imports are the risk.
src/ end to end — it's a small package, so this takes an hour. Look specifically for withoutMiddleware, authorize() calls on the resources, and whether the permission resource is itself policy-protected (a permission-editing screen that any Nova user can reach is a privilege-escalation hole).composer require fails with an unresolvable laravel/nova constraint, you likely need repositories entries or a Nova version the package predates. Check open issues for "Nova 5" support.--tag=config) and point the package at your existing tables via config/permission.php rather than running its migrations.guard_name mismatches surface as silent denials. $user->can('x') returns false with no error when the permission exists under a different guard. Debug with Permission::all()->pluck('guard_name','name') and compare to config('auth.defaults.guard').setUp() or set config(['permission.cache.store' => 'array']) in phpunit.xml.indexQuery with ->with('permissions') or accept the slowdown on small role counts (which is the normal case).fields(), add your own actions(), and override authorizedTo* per resource rather than forking. If the package's resources are final or the fields are hardcoded, that's a strong signal to write a 100-line custom Nova resource over Spatie instead — this package is small enough that reinventing it is cheap and removes a maintenance risk."ferdiunal/nova-shield": "1.2.3") and review diffs on upgrade, or vendor the source into app/Nova if the license permits (MIT — it does).How can I help you explore Laravel packages today?