bbs-lab/nova-permission

Manage roles and permissions in Laravel Nova with a visual permission builder, per-instance (granular) permissions and a super-admin override, built on spatie/laravel-permission.

Maintainers

Package info

github.com/BBS-Lab/nova-permission

pkg:composer/bbs-lab/nova-permission

Transparency log

Statistics

Installs: 9 276

Dependents: 0

Suggesters: 0

Stars: 6

Open Issues: 6

v2.0.0 2026-07-27 07:45 UTC

This package is auto-updated.

Last update: 2026-08-02 20:23:45 UTC


README

Latest Version on Packagist Software License Tests PHPStan Total Downloads

Based on spatie/permission, this tool gives you ability to manage roles and permission. The tool provides permission builder.

permission builder

Contents

Requirements

  • PHP ^8.2
  • Laravel Nova ^4.0 || ^5.0
  • spatie/laravel-permission ^6.0
  • Laravel ^11.0 || ^12.0 || ^13.0

Both Nova majors are exercised in CI. Note that Nova 4 (through its inertiajs/inertia-laravel dependency) tops out at PHP 8.4 and Laravel 11; on PHP 8.5 or Laravel 12+, use Nova 5. Composer resolves the right combination for you.

Installation

To get started, you will need to install the following dependencies

composer require bbs-lab/nova-permission

The service provider will automatically get registered. Or you may manually add the service provider in your config/app.php file:

'providers' => [
    // ...
    BBSLab\NovaPermission\NovaPermissionServiceProvider::class,
],

Publish Spatie's permission migration (the base permissions/roles tables):

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="permission-migrations"

This package's own migrations (which add the group + polymorphic authorizable columns and the override_permission flag) run automatically with php artisan migrate — you do not need to publish them.

You can publish the config files with:

php artisan vendor:publish --tag="nova-permission-config"

This publishes two files. config/permission.php is Spatie's standard configuration — the only change this package needs is to point the two model bindings at its own models (which implement the package contracts):

// config/permission.php (only the keys this package changes; the rest is Spatie's default)
return [

    'models' => [

        /*
         * When using the "HasPermissions" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your permissions. Of course, it
         * is often just the "Permission" model but you may use whatever you like.
         *
         * The model you want to use as a Permission model needs to implement the
         * `BBSLab\NovaPermission\Contracts\Permission` contract.
         */

        'permission' => BBSLab\NovaPermission\Models\Permission::class,

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your roles. Of course, it
         * is often just the "Role" model but you may use whatever you like.
         *
         * The model you want to use as a Role model needs to implement the
         * `BBSLab\NovaPermission\Contracts\Role` contract.
         */

        'role' => BBSLab\NovaPermission\Models\Role::class,

    ],

    // `table_names`, `column_names`, `display_permission_in_exception` and `cache`
    // keep Spatie's defaults — leave them as published unless you have a reason to change them.
];
// config/nova-permission.php
return [
    // Resources whose individual records can carry their own permissions
    // (see "Granular permissions" below). Leave empty if you only use
    // general, model-wide permissions.
    'authorizable_models' => [
        // \App\Nova\Post::class,
    ],

    // Resources excluded from `nova-permission:generate`.
    'generate_without_resources' => [
        \Laravel\Nova\Actions\ActionResource::class,
    ],

    // Authorization gate cache. Disabled by default — enable it only when the
    // read volume justifies it and you accept the staleness window (entries are
    // invalidated on permission save/delete). `ttl` is the lifetime in seconds.
    'cache' => [
        'enabled' => env('NOVA_PERMISSION_CACHE', false),
        'ttl' => env('NOVA_PERMISSION_CACHE_TTL', 60 * 60),
    ],
];

Then create the tables by running the migrations (Spatie's published migration plus this package's auto-registered ones):

php artisan migrate

Usage

You must register the tool with Nova. This is typically done in the tools method of the NovaServiceProvider:

// app/Providers/NovaServiceProvider.php
<?php

use BBSLab\NovaPermission\PermissionBuilder;
use Laravel\Nova\NovaApplicationServiceProvider;

class NovaServiceProvider extends NovaApplicationServiceProvider
{
    // ..
    
    public function tools()
    {
        return [
            // ...
            PermissionBuilder::make(),
            // You may add some access control
            PermissionBuilder::make()->canSee(function ($request) {
                return $request->user()->hasRole('admin');            
            }),
        ];
    }
}

Prepare your User model

On your authenticatable model, use this package's HasRoles trait (not Spatie's) and implement the CanOverridePermission contract. The package trait adds the per-instance hasPermissionToOnModel() method the policies rely on, and the contract powers the super-admin (override_permission) feature:

namespace App\Models;

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;
}

Important

Use BBSLab\NovaPermission\Traits\HasRoles, not Spatie\Permission\Traits\HasRoles. The Nova policies call hasPermissionToOnModel(), which exists only on the package trait — with Spatie's trait every protected resource throws Call to undefined method.

Generate permissions

The tool allow to generate resource permissions. Your resources must implement BBSLab\NovaPermission\Contracts\HasAbilities and define the public static $permissionsForAbilities variable :

namespace App\Nova;

use BBSLab\NovaPermission\Contracts\HasAbilities;
use BBSLab\NovaPermission\Traits\Authorizable;

class Post extends Resource implements HasAbilities
{
    use Authorizable;

    public static $permissionsForAbilities = [
//        'policy action' => 'display name'
        'create' => 'create post',
    ];
}

This configuration will generate the following permission:

[
    'name' => 'create post',
    'group' => 'Post', // class basename of the model
    'guard_name' => 'web', // the nova guard or default auth guard
]

You may generate permission from the permission builder tool with the Generate permissions button or the Artisan command:

php artisan nova-permission:generate

Protect resources

You may use authorization policies and extend the provided BBSLab\NovaPermission\Policies\Policy class

namespace App\Policies;

use App\Models\Post;
use BBSLab\NovaPermission\Policies\Policy;
use Illuminate\Contracts\Auth\Access\Authorizable;

class PostPolicy extends Policy
{
    protected function model(): string
    {
        return Post::class;
    }
}

Important

You must create a policy for each model you want to protect else the default permissions will be applied!

The base Policy class takes care of the following actions for you :

  • viewAny
  • view
  • create
  • update
  • replicate
  • delete
  • restore
  • forceDelete

You are free to add or update methods.

Granular permissions

Permissions come in two flavours:

  • General — a model-wide permission (the row's authorizable is null). "Can this user view posts?"
  • Granular — a permission scoped to one specific record via a polymorphic authorizable relation. "Can this user view this post?"

The two are resolved with an instance-override rule:

An instance that carries a granular permission is governed by that permission alone. An instance with no granular permission falls back to the general permission.

So granting view service generally lets a user view every service, until a granular view service is attached to a particular service — from then on that one service is gated by the granular grant only, while the rest keep following the general one.

To make a model's individual records authorizable, implement BBSLab\NovaPermission\Contracts\HasAuthorizations and use the BBSLab\NovaPermission\Traits\Authorizations trait:

namespace App\Models;

use BBSLab\NovaPermission\Contracts\HasAuthorizations;
use BBSLab\NovaPermission\Traits\Authorizations;
use Illuminate\Database\Eloquent\Model;

class Service extends Model implements HasAuthorizations
{
    use Authorizations;
}

Register the Nova resource in config/nova-permission.php so the builder lets you attach permissions to individual records:

'authorizable_models' => [
    \App\Nova\Service::class,
],

You can now create a permission scoped to a specific service:

permission on authorizable

Tip

To create the granular permission automatically whenever a record is created, use an observer.

Because the base Policy already routes per-instance abilities (view, update, delete, …) through hasPermissionToOnModel(), a policy that maps the ability name to the permission needs no extra code — the instance-override rule is applied for you. You only override a policy method when the granular permission has a different name than the ability:

namespace App\Policies;

use App\Models\Service;
use BBSLab\NovaPermission\Policies\Policy;
use Illuminate\Contracts\Auth\Access\Authorizable;
use Illuminate\Database\Eloquent\Model;

class ServicePolicy extends Policy
{
    protected function model(): string
    {
        return Service::class;
    }

    // The parameter type must stay `Model` to match the base Policy signature
    // (PHP forbids narrowing it to Service); the instance is your Service.
    public function view(Authorizable $user, Model $service): ?bool
    {
        if ($user->hasPermissionToOnModel('view secret service', $service)) {
            return true;
        }

        return null;
    }
}

Filtering an index by permission

To list only the records a user may act on, apply the authorize query scope (provided by the Authorizations trait). It mirrors the instance-override rule and honours override_permission roles:

Service::query()->authorize($request->user(), 'view service')->get();

Caching

Gate checks are not cached by default. Enable the cache from config/nova-permission.php (or the NOVA_PERMISSION_CACHE env flag) once read volume justifies it. Cached entries are invalidated when a permission is saved or deleted; see the config comments for the staleness caveats.

Super admin

You may want to create a role as super admin. You can do that using the override_permission attribute.

super admin

You may prevent override_permission attribute modification by using the BBSLab\NovaPermission\Resources\Role::canSeeOverridePermmission method:

// in a service provider

BBSLab\NovaPermission\Resources\Role::canSeeOverridePermmission(function (Request $request) {
    // implement your logic
});

Testing

The package ships a runnable Nova workbench that doubles as a manual playground and a demo of every feature:

composer serve   # boots Nova at http://127.0.0.1:8000/nova

Log in with admin@laravel.com / password (also seeded: writer@laravel.com, reader@laravel.com, same password). The workbench seeds roles, a Post and Service resource showcasing general vs granular permissions, and Nova impersonation so you can switch between the seeded users without logging out.

The quality gate:

composer test           # Pest
composer test-coverage  # Pest with 100% line coverage on src/
composer analyse        # PHPStan level 8
composer format         # Pint

Front-end work additionally uses npm run package (Prettier + ESLint + Vite build).

Changelog

Please see CHANGELOG for more information on what has changed recently, and UPGRADE for the 1.x → 2.0 migration.

Security

If you discover any security related issues, please email paris@big-boss-studio.com instead of using the issue tracker.

Contributing

Please see CONTRIBUTING for details.

Credits

License

The MIT License (MIT). Please see License File for more information.