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

yadahan/nova-bouncer

Provides Laravel Nova resources for Bouncer, the roles and permissions package. Manage roles, abilities, and entity assignments directly from Nova with a clean, searchable interface, letting admins grant or revoke access without touching code.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the stack before you install anything. yadahan/nova-bouncer is a Nova presentation layer over Bouncer. It requires (a) a licensed Laravel Nova install, and (b) models already using Bouncer's HasRolesAndAbilities trait. If you're on spatie/laravel-permission, this package is not for you — there is no adapter.

Install and register:

composer require yadahan/nova-bouncer
// app/Providers/NovaServiceProvider.php
public function tools()
{
    return [
        new \Yadahan\NovaBouncer\NovaBouncer, // confirm exact class name in the package README
    ];
}

Gate it in the same commit — never ship an ungated role editor:

Nova::tools(function ($request) {
    return [
        (new \Yadahan\NovaBouncer\NovaBouncer)->canSee(
            fn ($request) => $request->user()->can('manage-roles')
        ),
    ];
});

Where to look first, in this order:

  1. composer.json in the package. The laravel/nova, laravel/framework, and silber/bouncer constraints are the real compatibility matrix — the repo doesn't publish one. Check them against your lockfile before the install, not after.
  2. src/ and routes/. The package is small. Read what the tool's own routes authorize and what happens on save. You are going to own this code; skim it now while the cost is zero.
  3. resources/js/. When a Nova major moves under you, the breakage lives in the compiled frontend, not the PHP.
  4. config/bouncer.php in your app. The tool reads and writes through Bouncer, so guard and entity settings here bound what the UI can express.

First use case to ship (half a day): create one role in the UI, attach three abilities, assign it to a test user, then verify the outcome in application code — a policy check, a Gate::allows(), a Pest/PHPUnit assertion. Do not trust the UI's confirmation. That end-to-end loop — ticket replaced by a screen an ops lead can drive — is the entire value proposition. Prove it before you plan anything bigger.

Implementation Patterns

Structural data in seeders, volatile data in the UI. Treat the tool as an operational surface, not a schema designer. Roles and abilities are code artifacts: every ability string the UI can create is a string your policies must already understand, and a hand-typed typo in the UI is a silent no-op in production.

// database/seeders/AuthorizationSeeder.php — idempotent, safe on every deploy
Bouncer::allow('admin')->to(['users.view', 'users.edit', 'users.impersonate']);
Bouncer::role()->firstOrCreate(['name' => 'support'], ['title' => 'Support']);
Bouncer::ability()->firstOrCreate(['name' => 'posts.publish']);

Let admins use the UI for the part that genuinely changes daily — who holds which role. Keep what a role means in code review.

Give the tool its own permission boundary. "Can open the role manager" should be a distinct, auditable grant from "is an admin." Create the ability in a seeder, attach it to a narrow role, and check it in canSee() as above. Avoid the reflexive catch-all:

// DON'T: neutralizes every policy in the app
Gate::before(fn ($user) => $user->isSuperAdmin() ? true : null);

If you need a superadmin escape hatch, keep it, but always return null — never false — so normal policy resolution continues.

Wire cache invalidation into the same place you write the audit log. Bouncer caches ability checks. Any write through the UI must invalidate, or admins will "fix" a permission and see nothing change:

use Silber\Bouncer\Database\Models;

public function boot(): void
{
    Models::role()::observe(RoleObserver::class);
    Models::ability()::observe(AbilityObserver::class);
    Models::assignedRole()::observe(AssignedRoleObserver::class);
}
class AssignedRoleObserver
{
    public function created($pivot): void
    {
        activity()->causedBy(Auth::id())->withProperties([
            'role_id' => $pivot->role_id,
            'entity_id' => $pivot->entity_id,
            'entity_type' => $pivot->entity_type,
        ])->log('role assigned');

        Bouncer::refreshFor($pivot->entity);
    }

    public function deleted($pivot): void { /* same shape */ }
}

Nothing in the package records who changed what. Without an observer, a Friday-afternoon self-grant of users.edit leaves no trace.

Enforcement stays in policies. The tool writes reference data; your application reads it. Keep the dependency one-directional so a broken admin screen degrades to "engineers change roles in tinker," never to "authorization stops working."

// app/Policies/PostPolicy.php
public function update(User $user, Post $post): bool
{
    return $user->can('posts.edit') || $user->id === $post->user_id;
}

Decide tenant scoping explicitly. If the app is multi-tenant, Bouncer's scope determines what "assign this role" even means:

Bouncer::scope()->to($tenant->id);

Then answer the question out loud: does the Nova tool render globally or within a scope? A UI that shows global roles to a tenant admin is cross-tenant privilege escalation. If the package doesn't scope for you, either wrap it or restrict the tool to central staff and document that.

Write tests through Bouncer, not through the screen. Assert the outcome:

public function test_support_agent_can_view_users(): void
{
    $user = User::factory()->create();
    Bouncer::allow('support')->to('users.view');
    Bouncer::assign('support')->to($user);

    $this->assertTrue($user->can('users.view'));
}

Plus one thin Nova feature test asserting the tool's route is 403 for a non-privileged user. That second test is the only part the package actually owns, and it's the part most likely to regress on a Nova upgrade.

Name abilities with a convention, not vibes. Adopt resource.action (posts.publish, users.impersonate), centralize the strings in an enum or constants class consumed by seeders and policies, and treat an unrecognized ability string found in the database as a bug in review.

Gotchas and Tips

Version compatibility is the failure mode. Last release December 2024, 26 stars, zero public dependents, single maintainer. Nova majors break third-party tools routinely. Pin the version, spike in staging on the exact Nova + Laravel + PHP + Bouncer combination you intend to run, and set a review trigger: if a Nova upgrade breaks it, or upstream goes six months quiet, you fork. Budget for that outcome now instead of discovering it mid-incident.

Assume you own the code. MIT, tiny footprint, no SLA. That's an acceptable trade for 2–5 days of saved CRUD work — but make it a conscious decision, not a surprise. Read src/ once, seriously, before rollout.

Feature coverage will be partial. Bouncer supports wildcard abilities, ownership restrictions (Bouncer::allow($user)->toOwn($post)), entity-scoped abilities, and multiple guards. A generic CRUD UI rarely exposes all of that. Audit which of these your app depends on; anything the UI can't express becomes out-of-band DB work, which quietly defeats the "delegate to ops" rationale. If you rely on ownership or multi-guard, verify it in the spike or permanently scope those to engineering.

Lockout protection. Deleting the ability your superadmin gate depends on can lock every admin out of Nova — including out of the screen needed to fix it. Keep a break-glass artisan command that grants a superadmin role via Bouncer directly, and take a database snapshot of roles, abilities, and the pivot tables before bulk edits. migrate:rollback is not a rollback story here.

Cache staleness across processes. Bouncer::refresh() inside a web request doesn't clear an already-warm queue worker or Octane process. Restart workers on deploy if you run Octane; if queued jobs check abilities, invalidate inside the job boundary or accept staleness by design and write it down.

Large datasets degrade the UI. Thousands of roles/abilities, or a heavy entity list in the assignment picker, will make the screen unpleasant. Validate pagination, search, and eager loading against production-scale data during the spike. Bulk changes belong in artisan commands, not a browser.

Removal is clean — if you keep the data. Dropping the tool means removing it from tools() and composer remove. Bouncer's tables and all their rows survive untouched. That's a genuinely good rollback story; write it into the runbook so nobody reaches for migrate:fresh.

Debugging checklist when the screen misbehaves — answer in order: (1) Is the tool registered and not filtered out by canSee? (2) Are the frontend assets built/published for your Nova version? (3) Did the write actually land in roles/abilities/pivots? (4) Was Bouncer's cache refreshed? (5) Does the policy — not the UI — return the expected result? That sequence separates a UI bug from an authorization bug in about two minutes.

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.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator