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

Entrust Laravel Package

gghughunishvili/entrust

Entrust is a Laravel package for flexible role-based access control. Assign roles and permissions to users, protect routes with middleware, and manage authorization through a simple API and Blade helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

gghughunishvili/entrust is a maintained fork of the classic zizaco/entrust RBAC package, updated for Laravel 11/12/13. It gives you Role and Permission Eloquent models, role_user / permission_role pivot tables, traits for your User model, and middleware/Blade directives for authorizing routes and views. The mental model is unchanged from the original: permissions are attached to roles, roles are attached to users, and almost all checks are name-based string lookups.

1. Install and publish

composer require gghughunishvili/entrust
php artisan vendor:publish --provider="Gghughunishvili\Entrust\EntrustServiceProvider"

Check config/entrust.php first — it defines the role/permission model class names and table names. If you need custom models (very common), do it here before generating migrations.

2. Generate and run migrations

php artisan entrust:migration   # creates the roles/permissions/pivot tables
php artisan migrate

Read the generated migration before running it. Forks sometimes differ on column types (e.g. name length, unique indexes) and on whether a user-permission pivot exists.

3. Wire up the User model

use Gghughunishvili\Entrust\Traits\EntrustUserTrait;

class User extends Authenticatable
{
    use EntrustUserTrait;   // brings in HasRole + HasPermissions
}

4. Register middleware aliases (Laravel 11+ style)

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role'       => \Gghughunishvili\Entrust\Middleware\EntrustRole::class,
        'permission' => \Gghughunishvili\Entrust\Middleware\EntrustPermission::class,
        'ability'    => \Gghughunishvili\Entrust\Middleware\EntrustAbility::class,
    ]);
})

5. First use case — a seeder plus one guarded route

// database/seeders/RolesSeeder.php
$admin = Role::firstOrCreate(['name' => 'admin']);
$editor = Role::firstOrCreate(['name' => 'editor']);

$editPosts = Permission::firstOrCreate(['name' => 'edit-posts', 'display_name' => 'Edit Posts']);
$editor->attachPermission($editPosts);
$admin->attachPermission($editPosts);

User::where('email', '[email protected]')->first()->attachRole($admin);
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware(['auth', 'permission:edit-posts']);

That's the whole loop: seed roles/permissions → attach → guard routes/middleware → check in views with @permission. If you only ever need two or three fixed roles, this package is heavier than a boolean column; if you need runtime-editable permissions, it earns its place.

Implementation Patterns

Check permissions at the edge, roles in the domain. Use @permission / permission: middleware for the actual gatekeeping, and use hasRole() only for UI-level branching (e.g. showing an "Admin" nav section). Roles are a grouping mechanism; permissions are the thing you should be checking so you can regroup later without touching code.

@permission('edit-posts')
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@endpermission

@role('admin')
    <a href="/admin">Admin panel</a>
@endrole

Combine checks for multi-role logic. hasRole and can accept arrays, with an AND flag as the second argument:

$user->hasRole('admin');                    // single
$user->hasRole(['admin', 'editor']);        // OR
$user->hasRole(['admin', 'editor'], true);  // AND — both required
$user->can('edit-posts');                   // permission check via roles

The ability middleware is the ergonomic one for compound rules on a route:

Route::delete('/posts/{post}', ...)->middleware('ability:admin|edit-posts,edit-posts');
// first segment = roles (pipe-delimited), second = permissions (comma-delimited)

Super-admin bypass via a single Gate hook. Rather than sprinkling hasRole('admin') everywhere, put one escape hatch in a service provider:

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

Return null (not false) for everyone else so normal checks still run.

Treat can() as your authorization façade and let Policies delegate to it. If a controller action needs both a policy rule and a permission, do the permission check first — it's cheap and short-circuits:

public function update(Request $request, Post $post)
{
    abort_unless($request->user()->can('edit-posts'), 403);
    $this->authorize('update', $post);   // ownership etc.
}

Seed idempotently and version your permission names. firstOrCreate + syncWithoutDetaching in a seeder makes deploys repeatable. Keep permission strings in one place — a constants class or PHP enum — because there is no compile-time safety on the strings:

enum Permission: string {
    case EditPosts = 'edit-posts';
    case PublishPosts = 'publish-posts';
}
$role->attachPermission(Permission::EditPosts->value);

Cache aggressively in the read path, invalidate on write. Role/permission lookups are the hot path in an app with per-request checks. Eager-load when rendering lists of users:

$users = User::with('roles.permissions')->paginate(50);

and if you add your own permission cache, clear it inside the same transaction/service that calls attachRole/detachPermission.

Build an admin screen early. The package has no UI. A 30-line controller with $user->roles()->sync($request->roles) and $role->permissions()->sync($request->permissions) pays for itself in the first week and keeps non-developers out of your seeders.

Gotchas and Tips

This is a fork with 27 stars — treat upgrade risk as a real cost. The API is stable because it's inherited from zizaco/entrust, but "Laravel 13" support in a low-star fork means you are partly betting on one maintainer. Mitigations: pin the version in composer.json, and isolate the surface — centralize every hasRole/can call behind your own Gate definitions or a small AuthorizationService so swapping packages later is a one-file change, not a grep across 200 views.

can() may collide with Laravel's Authorizable trait. EntrustUserTrait brings its own permission-based can, which is not the same implementation as Laravel's Gate-backed one. Decide which one owns the method name in your User model, verify with a quick tinker check ($user->can('edit-posts') should reflect a role permission and your policies should still fire), and resolve conflicts explicitly with insteadof/as if PHP complains about trait collisions. If you rely heavily on Policies, wire Gate::before to consult Entrust so the two systems can't disagree.

Permission names are case-sensitive strings with no foreign-key protection. Rename a permission in a seeder and every @permission('edit-post') silently fails closed (or open, if you inverted a check). Renaming is a data migration, not a code change — write it as one.

The permission cache is per-request in the container; runtime changes can go stale. If you flip a role mid-request and then check can() again, don't assume a fresh answer. Call the package's cache-clearing method (or re-fetch the user) after any attachRole/detachRole in the same request, especially in tests where a single container serves many assertions.

Detaching is not symmetric with attaching. attachRole($role) is idempotent-ish, but detachRole on a user who doesn't have the role throws or no-ops depending on method used — prefer sync() on the relation for "set exactly these" semantics, which is what admin UIs actually want.

Middleware runs before your policy. A route with permission:edit-posts will 403 a user who has the permission but fails ownership — that's fine — but the reverse bites: a user who owns the post but lacks the permission can never reach the policy. For owner-scoped resources, consider a single ability or policy check instead of stacking middleware.

Blade directives can leak on cached views. After changing directive-registering code (or upgrading the package), run php artisan view:clear. A stale compiled view will keep the old behavior and produce a very confusing "the middleware blocks it but the button still shows" bug.

Watch the query count on index pages. @permission inside a @foreach over 50 rows is 50+ queries without eager loading. User::with('roles.permissions') is the fix; measure with DB::listen or Telescope before you ship a user list.

Config changes require a cache reset. If you swap models.role for your own App\Models\Role, run php artisan config:clear — the published config is merged into the cached config and a stale model class name produces a Class not found deep inside the package, which is a miserable debug.

Extension points worth knowing: custom Role/Permission models (add display_name, description, tenant scoping via a global scope), overriding EntrustRole/EntrustPermission middleware for custom unauthorized responses (JSON vs. redirect), and the Entrust facade for filter-style checks like Entrust::routeNeedsRole('admin/*', 'admin') if you prefer that over middleware aliases.

Testing: use RefreshDatabase, seed a minimal role/permission fixture in setUp, and assert both directions — assertTrue($user->can('edit-posts')) and $this->actingAs($user)->get('/posts/1/edit')->assertForbidden() when it should fail. Testing only the happy path is how permission regressions reach production.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core