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 Acl Laravel Package

masoudi/nova-acl

masoudi/nova-acl adds role-based access control to Laravel Nova. Manage roles and permissions, assign them to users, and restrict access to Nova resources, actions, and tools through an admin-friendly interface designed for Laravel projects using Nova.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a 2-star, archived, zero-dependent package. Treat it as a starting point or a reference implementation, not a production dependency. Before installing anything, open the repo and answer three questions in this order:

  1. composer.json — what does it require? If it pins laravel/nova to ^3.0 or ^4.0, it will not work with current Nova. Check illuminate/* constraints too.
  2. src/ — is there a NovaServiceProvider, a ToolServiceProvider, or plain Resource classes? This tells you whether it registers itself or whether you must register resources manually in NovaServiceProvider::resources().
  3. database/migrations/ — does it create its own roles/permissions tables, or does it assume spatie/laravel-permission already exists? This is the single most important thing to determine, because getting it wrong means table-name collisions or missing columns.

Install (knowing it's archived):

composer require masoudi/nova-acl

If the package ships migrations and a config file, publish them before running anything:

php artisan vendor:publish --provider="Masoudi\NovaAcl\NovaAclServiceProvider"
php artisan migrate

If vendor:publish lists nothing, there is no service provider — you'll need to register the resource classes by hand in app/Nova/NovaServiceProvider.php.

First use case — the one thing this kind of package is built for: assign a role to a user from inside Nova, and have Gate respect it. Concretely: log into Nova, open a User detail page, find a role field or a "Roles" relationship panel, attach admin, then confirm Gate::allows('viewAny', Post::class) flips from false to true. Do this once on a throwaway user before wiring it into anything else — it validates the whole chain (model trait, migration, policy integration, cache).

If Nova shows no new menu entries after install, the package registers resources only when Nova::resources() picks them up — clear the Nova cache:

php artisan nova:cache-clear
php artisan optimize:clear

Implementation Patterns

Assume it wraps spatie/laravel-permission unless proven otherwise. Most "Nova ACL" packages are thin UI layers over Spatie. Confirm by grepping for HasRoles and Spatie\Permission in src/:

grep -r "Spatie\|HasRoles\|HasPermissions" vendor/masoudi/nova-acl/src

If it does, you get Spatie's API for free and the package is only worth it for the resource screens — you can drop the package later and keep the data.

Attach the trait to your User model (this is almost never done for you):

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

Wire permissions into policies — not middleware. The day-to-day pattern is:

class PostPolicy
{
    public function viewAny(User $user): bool
    {
        return $user->hasPermissionTo('posts.view');
    }

    public function update(User $user, Post $post): bool
    {
        return $user->hasPermissionTo('posts.update');
    }
}

Then register Gate::before once in AuthServiceProvider for a super-admin bypass. This is the pattern nearly every Nova admin app ends up with:

public function boot()
{
    Gate::before(function ($user, $ability) {
        return $user->hasRole('super-admin') ? true : null;
    });
}

Returning null (not false) is what lets normal policies still run. Returning false silently kills every permission for non-admins — a very common bug.

Seed roles rather than clicking them in. Whatever the Nova screens do, roles are config, and config should be in code so a fresh environment matches production:

// database/seeders/RoleSeeder.php
$admin = Role::create(['name' => 'admin']);
$admin->givePermissionTo(['posts.view', 'posts.create', 'posts.update', 'posts.delete']);
User::where('email', '[email protected]')->first()->assignRole('admin');

Integrate with Nova fields for the per-record case. If the package exposes a Nova field (e.g. a role selector), place it explicitly rather than relying on auto-discovery:

public function fields(NovaRequest $request)
{
    return [
        Text::make('Name')->sortable(),
        // role assignment on the user resource
        Role::make('Roles')->hideFromIndex(),
    ];
}

Guard everything at the API boundary too. Nova's ACL rarely protects your front-end routes. If you expose the same models over an API, mirror the policy checks — $this->authorize('update', $post) in the controller — because permissions stored in the DB mean nothing without a Gate::authorize call somewhere.

When the package doesn't do what you need, wrap it, don't fork it. Since it's archived, put your customization in your own app/Nova/Resources/Role.php and your own policies. Extend, don't vendor-edit — you'll never get upstream fixes, and a composer update will erase your work if you're editing inside vendor/.

Gotchas and Tips

  • Archived + last release Feb 2023 + 0 dependents = you are the maintainer. Expect incompatibility with Nova 5 / Laravel 11 (PHP 8.2+ typed properties, illuminate/support ^11.0 constraints). Pin your Nova version and audit the composer.json constraints before running composer update on anything else in the project.
  • Table collisions are the #1 failure mode. If the package ships its own roles/permissions migrations and you also have spatie/laravel-permission's migrations, you will get "table already exists" or, worse, silently mismatched columns. Decide on one schema before running migrate.
  • Permission cache is the #1 debugging trap. Spatie caches permissions; hasPermissionTo() returning stale false after a role change is almost always the cache. php artisan permission:cache-reset (or app(PermissionRegistrar::class)->forgetCachedPermissions()) fixes it. Never change roles in a tinker session and expect web requests to see it immediately.
  • Gate::before returns are load-bearing. true = always allow, false = always deny, null = defer to the policy. Returning a role check's boolean directly (return $user->hasRole('admin');) forces false on all non-admins and bypasses every policy you wrote. Use the ternary-to-null form shown above.
  • Guard names matter. Spatie permissions are scoped by guard (web vs api). If your API users authenticate via Sanctum and your roles were seeded with guard_name = 'web', hasPermissionTo('x') on the API guard returns false. Either set guard_name explicitly everywhere or use hasPermissionTo('x', 'web'). Check the config's guard key.
  • N+1 on permission checks in index views. hasPermissionTo() can hit the cache and, on miss, the DB. In a Nova index of 100 users each rendering a role column, that's painful. Eager-load: User::with('roles')->... in the resource's indexQuery.
  • Migration ordering. If the package's migration references an App\Models\User table by foreign key, it must run after your users migration. If it runs first, MySQL may error or silently create no FK. Check timestamps in vendor/.../migrations/ and bump the filename if needed.
  • Config quirks. If a config/nova-acl.php is published, check for keys that toggle features (e.g. register_resources, super_admin_role, cache_ttl). A published config with a hardcoded role name that doesn't match your seeded role is a silent no-op.
  • Extension points to prefer over modification: your own NovaServiceProvider::resources(), your own Role/Permission Nova resources, your own policies, and a Gate::before in AuthServiceProvider. All four live outside vendor/ and survive updates (or the absence of them).
  • Debugging checklist when permissions "don't work": (1) trait present on model — class_uses(User::class); (2) record exists — Role::where('name','admin')->first(); (3) guard matches the authenticated guard; (4) cache cleared; (5) Gate::forUser($user)->allows(...) in tinker to isolate the UI layer from the auth layer. If tinker says yes and Nova says no, the problem is the package's UI, not your ACL — which, given it's archived, you may just want to replace with two hand-written Nova resources.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin