NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Permissions for Laravel

Gate & middleware

With register_gate_check on (the default), permissions resolve through Laravel’s Gate, so the native can: middleware works out of the box:

Route::get('/users', UsersController::class)->middleware('can:auth.users.view');

$user->can('auth.users.view');           // true when granted directly or via a role
Gate::forUser($user)->allows('auth.users.view');

How the hook decides

The package registers a single Gate::before hook. It answers only when all of the following hold — otherwise it returns null and your own gates and policies run untouched:

  • The check carries no arguments — no model and no extra parameters.
  • The user model has hasPermissionTo(), i.e. it uses HasRoles.
  • The ability is the name of a registered permission.

When the user holds that permission — directly or via a role — the hook returns true. It never returns false: a user without the permission falls through to your gates, and with none defined Laravel denies, so the can: middleware responds with 403.

Policies keep the final say

Permission checks never override model policies. As soon as a check carries a model or any argument — $user->can('update', $post), or the can:update,post middleware — the hook defers to your gate or policy. A permission named update authorizes $user->can('update'), but PostPolicy::update() still decides $user->can('update', $post):

Permissions::permission('update');
Permissions::for($user)->givePermissionTo('update');

$user->can('update');          // true — argument-less, answered by the permission
$user->can('update', $post);   // PostPolicy::update() decides — ownership, tenancy, …
final class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $post->owner_id === $user->getKey();
    }
}

Coarse permissions and model-scoped policies coexist without one silently swallowing the other.

Disabling the hook

Set register_gate_check to false to skip the hook entirely and resolve permissions yourself:

// config/permissions.php
'register_gate_check' => false,

// then check explicitly where you need it
abort_unless($request->user()->hasPermissionTo('auth.users.view'), 403);

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.