Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Permission Laravel Package

bbs-lab/nova-permission

bbs-lab/nova-permission adds a Laravel Nova UI for spatie/laravel-permission, letting you create roles and permissions, assign them to users, and manage access control directly from Nova without custom admin screens.

View on GitHub
Deep Wiki
Context7

Getting Started

v2.0.0 is effectively a re-release of the granular-permission feature — treat it as a fresh install even if you used 1.x, because the old implementation could not actually create scoped permissions.

1. Check the requirements before touching composer. PHP ^8.2, Laravel Nova ^4.0 || ^5.0, spatie/laravel-permission:^6. If you are pinned to Spatie 5 or Nova 3, stay on 1.x.

composer require bbs-lab/nova-permission:^2.0

2. Publish config and migrate. Only Spatie's migration is publishable now; the package's own migrations are real .php files that ship in the package and run automatically.

php artisan vendor:publish --provider="BBSLab\NovaPermission\NovaPermissionServiceProvider"
php artisan vendor:publish --tag="permission-migrations"   # Spatie's table only
php artisan migrate

3. Put the package trait on your authenticatable model — not Spatie's. This is the single step people get wrong. The policies call hasPermissionToOnModel(), which only exists on the package trait.

use BBSLab\NovaPermission\Contracts\CanOverridePermission;
use BBSLab\NovaPermission\Traits\HasRoles;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements CanOverridePermission
{
    use HasRoles; // NOT Spatie\Permission\Traits\HasRoles
}

4. Register the tool in app/Providers/NovaServiceProvider.php (see the package's README for the exact tool class in your Nova version), then visit the Permissions and Roles screens in Nova.

First use case to try: open the workbench demo (composer install && composer run serve in a clone). It seeds admin/writer/reader roles plus a Post resource (general permissions) and a Service resource (granular/per-instance permissions), so you can see both permission models side by side before wiring anything into your own app.

Where to look first in the docs: Requirements → Granular permissions → Caching. Skip the caching page until granular permissions work, because the cache defaults to off and won't confuse you until you enable it.

Implementation Patterns

General (role-level) permissions. The classic Spatie flow is unchanged: create permissions in the Nova UI, attach them to roles, assign roles to users, and check them normally in policies or Blade. The package's Nova screens are the admin surface for this; you rarely touch the models directly.

Granular (per-instance) permissions. This is the feature v2.0.0 actually delivers. You grant a permission scoped to a specific model instance instead of globally:

$user->givePermissionToOnModel('update-post', $post);

The composite unique index on (name, guard_name, authorizable_id, authorizable_type) is what makes a same-named permission coexist across many instances — 1.x silently couldn't do this.

Policies: extend the package base and keep the signature wide.

use BBSLab\NovaPermission\Policies\Policy;
use Illuminate\Database\Eloquent\Model;

class PostPolicy extends Policy
{
    public function update(User $user, Model $model): ?bool
    {
        return $user->hasPermissionToOnModel('update-post', $model);
    }
}

Return ?bool deliberately: null means "no opinion", letting other gates or a before() hook decide. Returning false hard-denies and short-circuits the rest of the chain.

Caching as a deliberate switch. Leave cache.enabled at false while you build, then turn it on in production:

NOVA_PERMISSION_CACHE=true
NOVA_PERMISSION_CACHE_TTL=3600

The package invalidates per-model authorization cache entries on save, so you get the speedup without hand-clearing caches on every permission edit.

Testing. The package ships Pest tests at 100% coverage, and the workbench is a working reference. Mirror its shape: seed roles in a TestCase/Pest.php helper, use Nova impersonation (Nova::impersonate($user)) to exercise tool screens, and assert both the general and the per-instance paths for the same permission name — that pair is exactly what regressed in 1.x.

Integration tip for existing apps: put granular checks in a single place, e.g. a Gate::before or a small AuthorizationService, rather than scattering hasPermissionToOnModel() through controllers. It keeps the upgrade surface small if the signature changes again.

Gotchas and Tips

  • Trait swap is mandatory, and it's not a silent failure. Using Spatie's HasRoles compiles fine and fails at policy-evaluation time with a missing-method error. If every policy suddenly denies, check the use statement first.
  • The interface is required too. CanOverridePermission isn't cosmetic — the policies type against it. A model that uses the trait but doesn't implement the contract produces confusing behavior instead of a clean error.
  • Upgrading an existing 1.x database will not fix itself. The auto-running migrations are for new installs; your old 1.x migration already ran and left Spatie's unique(name, guard_name) index in place, so scoped permissions still can't be inserted. You need a manual migration dropping that index and adding the composite one. Same for anyone who copied the old migration into database/migrations.
  • Watch the index name length. The package uses an explicit, MySQL-safe index name precisely because the auto-generated one blows past MySQL's 64-character identifier limit on long table names. If you hand-roll the migration, copy the name from the package's file.
  • gate_cache is gone. Any config/nova-permission.php you published in 1.x with gate_cache => true is now inert. Move to the cache section or you'll silently lose caching (usually the benign direction, but don't assume it).
  • Never narrow the policy parameter. Overriding update(User $user, Post $post) against a base Model $model is a fatal signature incompatibility in PHP. Keep Model $model and type-check inside if you need to.
  • Debugging "permission denied but it's in the UI": in order — (1) cache.enabled and whether a stale entry survived, run php artisan permission:cache-reset; (2) guard name mismatch (web vs sanctum vs Nova's guard); (3) whether the check is hasPermissionTo() (global) when it should be hasPermissionToOnModel() (scoped). Those three account for nearly every report.
  • scopeAuthorize() resolves table and key dynamically now. If you copied the 1.x query or wrote a custom scope against a hardcoded chambers.id, it's broken by design — that column was leftover from the maintainer's other project.
  • Nova 4 is supported again, but only from v2.0.0. Nova 4 users who pinned 1.x after it "mysteriously broke" should retest; a compatibility shim now bridges the authorization differences between 4 and 5. If you override any package views or components, diff them against the new versions rather than assuming they're unchanged.
  • Permission/role class resolution is configurable. If you extend Spatie\Permission\Models\Permission, point the package config at your subclass, or the Nova screens will edit the wrong model. v2.0.0 fixed the resolution itself; misconfiguration now surfaces more obviously than before.
  • Treat the workbench as documentation. The seeded roles and the Post/Service split are the clearest explanation of general vs granular semantics anywhere in the project — useful to hand to a teammate onboarding onto the admin UI.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager