# JosephSilber/bouncer: roles and abilities for Eloquent models

> Bouncer stores roles and abilities in the database and hooks into Laravel's gate, so authorization checks fall through to it automatically. It targets Laravel 11+ and PHP 8.2+, and it can be used outside Laravel through the Eloquent Capsule.

**JosephSilber/bouncer** — Laravel Eloquent roles and abilities.

- Repository: https://github.com/JosephSilber/bouncer
- Stars: 3,578 · Forks: 334
- Language: PHP
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/josephsilber-bouncer

## The problem Bouncer solves for Eloquent applications

Laravel's gate is a good place to check whether a user may do something, but it does not decide where that permission comes from. Bouncer answers that question by storing roles and abilities in database tables and consulting them whenever the gate is asked. The README describes it as "an elegant, framework-agnostic approach to managing roles and abilities for any app using Eloquent models." The audience is Laravel developers who want permissions to be data rather than code, and who want to change them without a deploy.

The scope is deliberately narrow. Bouncer is not an admin panel, a policy generator, or a reporting tool. It gives you a fluent API to grant and revoke, and it stays out of the way otherwise. The README puts it plainly: use it when you want, ignore it when you don't. That is a real design position, and it explains both what the package is good at and what it leaves to you.

## How Bouncer hooks into Laravel's gate

The mechanism is a trait plus a gate hook. You add Silber\Bouncer\Database\HasRolesAndAbilities to your user model, and Bouncer registers itself with Laravel's gate. From then on, when your application checks an ability, Bouncer is consulted. If the current user has been granted that ability directly or through a role, the check passes.

Grants can target a class or a single record. Bouncer::allow($user)->to('create', Post::class) grants a class-level ability, while Bouncer::allow($user)->to('edit', $post) grants it only for that one post. The README also documents ownership, so a user or role can be allowed to own a model, and it documents forbidding an ability, which is the explicit deny path. Roles are separate records that you assign to users, and abilities can be attached to a role instead of a user.

One behaviour is worth calling out because it affects how you reason about failures. The README states that your code always takes precedence: if your code allows an action, Bouncer will not interfere. So Bouncer is not the final authority in a request. It is a fallback consulted by the gate, which means a hard-coded allow in your own code will win over anything Bouncer denies. If you expect Bouncer to be the single source of truth, you will need to keep other allow paths out of the way.

## Installing Bouncer in a Laravel app and granting a first ability

The README notes that Bouncer v1.0.2 requires PHP 8.2+ and Laravel/Eloquent 11+. If you are on Laravel v6 to v10, the README points to Bouncer v1.0.1; for Laravel v5.5 to v5.8 it points to Bouncer RC6.

Install the package with Composer. The README gives this exact command:

```bash
composer require silber/bouncer
```

Next, add the trait to your user model. Without it, the model has no roles or abilities to check:

```php
use Silber\Bouncer\Database\HasRolesAndAbilities;

class User extends Model
{
    use HasRolesAndAbilities;
}
```

Publish Bouncer's migrations into your application's migrations directory, then run them. The README uses the tag bouncer.migrations:

```bash
php artisan vendor:publish --tag="bouncer.migrations"
php artisan migrate
```

After migrating, grant an ability. This example from the README gives a user permission to create posts, and also shows the role-based route:

```php
use Bouncer;

Bouncer::allow($user)->to('create', Post::class);

Bouncer::allow('admin')->to('create', Post::class);
Bouncer::assign('admin')->to($user);
```

If you use the Bouncer facade, remember the use Bouncer; import at the top of the file, as the README instructs. For a non-Laravel app, the README directs you to set up the database with the Eloquent Capsule component, then either run the migrations through a tool such as vagabond or execute the raw SQL in migrations/sql/MySQL.sql directly. The README does not document a rollback procedure for those migrations.

## Caching, the bouncer:clean command, and what goes stale

Bouncer can cache its lookups, and the README has a dedicated section on enabling cache plus a "Refreshing the cache" section. That tells you the package expects you to manage cache invalidation yourself when permissions change outside the normal API paths. If you edit roles or abilities directly in the database, the cached answer can disagree with the table until the cache is refreshed.

The package ships one console command, bouncer:clean, listed under "Console commands" in the README. It is the documented way to clear Bouncer's cached data. The README does not describe a schedule, a TTL, or an automatic invalidation hook, so the refresh is something you trigger. That is the main operational cost of using Bouncer with cache enabled: a permission change is not necessarily visible immediately, and the only documented remedy is running the command.

This is a trade-off rather than a defect. Reading permissions from cache keeps the gate cheap on every request. The price is a window where a revoked ability still passes, which matters if you use Bouncer for anything security-sensitive and revoke access as an incident response.

## Multi-tenancy and the scope middleware

Bouncer has first-class support for multi-tenant applications. The README documents a scope middleware and a way to customize Bouncer's scope, and the repository has a top-level middleware/ directory alongside src/, migrations/ and tests/. The idea is that the same user can hold different roles in different tenants, and Bouncer resolves which set applies based on the current scope.

This is the feature that most distinguishes Bouncer from a plain roles table, and it is also the one that requires the most care. Scope resolution depends on middleware running before the ability check. If a route bypasses that middleware, or if a queued job runs outside the request lifecycle, the scope may not be what you expect. The README documents how to customize the scope but does not enumerate every context in which it is applied, so testing your own job and command paths is on you. For a single-tenant application this whole area is dead weight you can ignore.

## Where Bouncer is the wrong tool

Bouncer is not a policy framework, and it does not replace Laravel policies. Policies express logic that depends on the state of a model, such as whether a post is still a draft or whether the requester is the author. Bouncer's ownership concept covers the simple case of a user owning a record, but anything more conditional belongs in a policy. Mixing the two without a clear rule about which layer decides leads to permission checks that are hard to trace, especially given that your own code takes precedence over Bouncer.

There is also no admin interface. Bouncer gives you a PHP API; building the screens that let an administrator assign roles is your work. If you need an out-of-the-box permissions UI, or audit logging of who granted what and when, Bouncer does not provide either. The README's FAQ even addresses where to set up roles and abilities, which is a sign that the package expects you to decide that yourself rather than prescribing a seeding strategy.

Finally, the version requirements are strict. On Laravel v6 to v10 you must stay on Bouncer v1.0.1, and older Laravel branches pin you to RC6. That constrains upgrade planning: moving your application to Laravel 11 also means moving Bouncer to the 1.0.2 line, and the README treats those as separate tracks.

## How Bouncer differs from spatie/laravel-permission

The README has an "Alternative" section, and the package most Laravel developers compare against is spatie/laravel-permission. The difference is in emphasis rather than in the basic idea, since both store roles and permissions in the database and both integrate with Laravel's authorization.

Bouncer puts weight on granting an ability against a specific model instance and on ownership, and it documents multi-tenancy with a scope middleware as a first-class concern. Its API is built around the Bouncer facade and the HasRolesAndAbilities trait, and it explicitly states that your own code takes precedence over its decisions. If your permission model is mostly "this user may edit this particular record, or records they own, in this tenant," Bouncer's vocabulary maps onto that directly.

The practical way to choose is to write down the checks you actually perform. If most of them are role-to-permission lookups at the class level, either package will do and the deciding factor is which API your team finds clearer. If a meaningful share of them are per-record or per-tenant, Bouncer's model is the closer fit and saves you from encoding that logic yourself on top of a simpler package.

## Licence, maintenance and upgrade cost

Bouncer is MIT licensed, and the repository includes LICENSE.txt at the top level. MIT is permissive: you can use the package in commercial and closed-source applications, and you are not obliged to publish your own code. The usual obligation is preserving the copyright notice and licence text in distributions of the software itself. That is a summary of the licence identifier, not legal advice; read LICENSE.txt if the distinction matters to your organisation.

The repository is not archived, and the most recent push recorded is 2026-03-18, which is the same date as the v1.0.4 release. Before that, v1.0.3 is dated 2025-02-24. So releases arrive occasionally rather than continuously, and the gap between v1.0.3 and v1.0.4 is roughly a year. Plan for a package that you upgrade deliberately rather than one that tracks Laravel's release cadence closely.

The upgrade cost is concentrated in two places. Version compatibility is the first: the README ties Bouncer v1.0.2 to PHP 8.2+ and Laravel/Eloquent 11+, and older Laravel versions to older Bouncer tags, so a framework upgrade forces a Bouncer decision at the same time. The second is the published migrations. Once you have published them into your application, they are yours; the README documents the publish and migrate steps but not a rollback, so treat the schema as something you own from that point on.

## Conclusion

Adopt Bouncer when you want database-stored roles and abilities that plug straight into Laravel's gate and you are willing to run its migrations and keep its cache fresh. Skip it if you need an admin UI, an audit trail, or per-request policy logic that is better expressed in Laravel policies. Before committing, verify three things: that your PHP and Laravel versions match the v1.0.2 requirement of PHP 8.2+ and Laravel/Eloquent 11+, that the published migrations run on your database engine (the FAQ documents MySQL key-length and JSON errors), and that your model classes are stable enough for class-name strings such as Post::class to remain valid in the abilities table.

## FAQ

### How do I install Bouncer in a Laravel app?

Run composer require silber/bouncer, add the HasRolesAndAbilities trait to your user model, publish the migrations with php artisan vendor:publish --tag="bouncer.migrations", and run php artisan migrate. The README notes that Bouncer v1.0.2 requires PHP 8.2+ and Laravel/Eloquent 11+.

### How do I use Bouncer to grant an ability?

Call Bouncer::allow($user)->to('create', Post::class) to grant a class-level ability, or pass a model instance instead of the class to grant it for one record only. You can also grant the ability to a role and then assign that role to the user with Bouncer::assign('admin')->to($user).

### How do I set Bouncer up outside of Laravel?

Install it with Composer, then set up the database using the Eloquent Capsule component as the README describes. Run the migrations either through a tool such as vagabond or by executing the raw SQL in migrations/sql/MySQL.sql, and add Bouncer's trait to your user model.

### What is a bouncer?

In this context, Bouncer is a PHP package by JosephSilber that manages roles and abilities for applications using Eloquent models, storing them in database tables and consulting them through Laravel's gate. The README describes it as a framework-agnostic approach to roles and abilities.

## Sources

- [Issues](https://github.com/JosephSilber/bouncer/issues)
- [JosephSilber/bouncer on GitHub](https://github.com/JosephSilber/bouncer)
- [License: MIT](https://github.com/JosephSilber/bouncer/blob/master/LICENSE)
- [README](https://github.com/JosephSilber/bouncer/blob/master/README.md)
- [Releases](https://github.com/JosephSilber/bouncer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/josephsilber-bouncer
