vattra / laravel-rbac
Fast, config-driven role-based access control for Laravel
Requires
- php: ^8.2
- laravel/framework: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-23 06:35:41 UTC
README
Fast, config-driven role-based access control for Laravel 10, 11, 12 and 13.
- Roles and permissions defined in
config/rbac.php, synced with one command HasRolesAndPermissionstrait for your User model- Gate integration,
rbac.role/rbac.permissionmiddleware, Blade directives - Policy-safe: permissions never bypass your policies
- Configurable table names and user keys (bigint, UUID, ULID)
- Optional persistent cache, flushed automatically
Requirements: PHP 8.2+ (Laravel 13 needs PHP 8.3+), Laravel 10-13.
Installation
composer require vattra/laravel-rbac php artisan vendor:publish --tag=rbac-config # optional but recommended php artisan migrate # migrations load from the package php artisan rbac:install # sync roles + permissions from config php artisan rbac:check # verify the setup
Add the trait to your User model:
use Vattra\Rbac\Traits\HasRolesAndPermissions; class User extends Authenticatable { use HasRolesAndPermissions; }
Publish tags: rbac-config, rbac-migrations, and rbac (both).
Configuration
config/rbac.php (generic defaults: super_admin, admin, editor, user).
| Key | Purpose |
|---|---|
super_admin_role |
Role that bypasses every permission/role check (default super_admin) |
admin_role |
Used by the is_admin gate and *except_rbac (default admin) |
user_model |
User class; null falls back to auth.providers.users.model (RBAC_USER_MODEL env) |
user_table, user_foreign_key, user_key_type |
Users table, pivot column, id / uuid / ulid |
tables.* |
Names of the five tables |
cache.enabled, cache.ttl, cache.store, cache.prefix |
Persistent cache (RBAC_CACHE env) |
register_role_gates |
Define an is_<role> gate per configured role |
legacy_middleware_aliases |
Also register role / permission aliases |
roles |
Role definitions (see below) |
rbac_protected_permissions |
Excluded from the *except_rbac wildcard |
'roles' => [ 'super_admin' => ['type' => 'system', 'locked' => true, 'permissions' => '*'], 'admin' => ['type' => 'admin', 'locked' => true, 'permissions' => '*except_rbac'], 'editor' => ['type' => 'staff', 'permissions' => ['view_posts', 'store_posts']], ],
Permission names follow <action>_<group>; the group is the last _ segment
(view_blog_posts -> posts). locked roles cannot be deleted.
UUID users / custom tables. Set these before running php artisan migrate:
'user_table' => 'members', 'user_foreign_key' => 'member_id', 'user_key_type' => 'uuid', // 'id' | 'uuid' | 'ulid' 'tables' => ['roles' => 'acl_roles', /* ... */],
Usage
Roles and permissions
$user->assignRole('admin', 'editor'); $user->removeRole('editor'); $user->syncRoles(['user']); $user->hasRole('admin'); // any of the given roles $user->hasAnyRole('admin', 'editor'); $user->hasAllRoles('admin', 'editor'); $user->getRoleNames(); // Collection $user->givePermissionTo('destroy_posts'); // direct permission $user->revokePermissionTo('destroy_posts'); $user->hasPermissionTo('view_posts'); $user->hasAnyPermission(['a', 'b']); $user->hasAllPermissions(['a', 'b']); $user->getAllPermissions(); // via roles + direct User::role('editor')->get(); // also 'admin|editor' User::permission('view_posts')->get(); // via role, direct or super admin
The super admin passes every hasPermissionTo() / hasAnyPermission() check,
including permission names that don't exist.
Role helpers: Role::givePermissionTo(), revokePermissionTo(), syncPermissions().
Look things up with the facade: Rbac::findRole('admin'), Rbac::findPermission('view_posts').
Middleware
Route::middleware('rbac.role:admin|editor')->group(...); // or admin,editor Route::middleware('rbac.permission:view_posts')->get(...); // or a|b
Any of the listed roles/permissions is enough, and the super admin always passes.
Guests get a redirect to login (or 401 for JSON requests / apps with no login
route); authenticated users who lack access get 403.
Blade
@role('admin|editor') ... @elserole('user') ... @endrole @hasanyrole('admin', 'editor') ... @endhasanyrole @permission('view_posts') ... @endpermission
Gates
Permission names work as abilities, and is_admin, is_staff and is_<role> gates exist:
Gate::allows('view_posts'); $user->can('view_posts'); @can('view_posts') ... @endcan
The package's Gate::before only ever grants. It does nothing for guests, and it
never overrides a policy: when a policy applies to the arguments (e.g.
Gate::allows('view_posts', $post) and Post has a policy) the policy decides. The
super admin bypasses everything that isn't governed by a policy.
Policies
Extend RbacPolicy to let the super admin through your policy automatically and
use the hasPermission() helper:
use Vattra\Rbac\Policies\RbacPolicy; class PostPolicy extends RbacPolicy { public function update(User $user, Post $post): bool { return $this->hasPermission($user, 'update_posts') && $user->id === $post->user_id; } }
Cache
Set RBAC_CACHE=true to cache each user's roles/permissions and the registered
permission list (rbac.cache.ttl, rbac.cache.store). Changes made through the
trait, the Role/Permission models, Role::givePermissionTo() and the install
commands flush it automatically. If you write to the pivot tables with raw SQL,
call Rbac::flushCache().
Commands
| Command | Description |
|---|---|
rbac:install |
Create/sync roles and permissions from config |
rbac:seed |
Alias for rbac:install |
rbac:sync-permissions |
Alias for rbac:install |
rbac:assign-role {user} {role} [--sync] |
Assign a role by key or email (uses rbac.user_model) |
rbac:check |
Verify tables, user model, gates, seeded roles (non-zero exit on problems) |
rbac:install is the source of truth for configured roles: it re-syncs each
configured role's permissions, replacing any changes made to them in the database.
Testing
composer install
composer test
The suite uses PHPUnit and Orchestra Testbench (in-memory SQLite). CI runs PHP 8.2-8.4 against Laravel 10-12 (plus 13 on PHP 8.3+).
Upgrade notes (0.1 -> 0.2)
rbac:migratewas removed (its--freshflag ranmigrate:fresh). Usephp artisan migrate.- Middleware aliases are now
rbac.roleandrbac.permission. To keep the old names, setlegacy_middleware_aliasestotrue. - Gate::before no longer bypasses policies. If you relied on a permission overriding a policy, move that check into the policy.
- Soft deletes were removed from roles/permissions. The new migration purges
soft-deleted rows and drops
deleted_at; runphp artisan migrate. - The hardcoded
is_sales_rep,is_inventory_manager,is_project_manager,is_finance_officer,is_clientgates are gone.is_<role>gates are now generated from yourrolesconfig, so those names return if you keep those roles. - The default config is generic. If you published the old ERP config, keep it (the example below).
- Add
'admin_role' => 'admin'and the new keys if you published the config earlier. - Folder
Database/is nowdatabase/; the seeders were removed (userbac:install).
Example: ERP-style roles
'roles' => [ 'super_admin' => ['type' => 'system', 'locked' => true, 'permissions' => '*'], 'admin' => ['type' => 'admin', 'permissions' => '*except_rbac'], 'sales_rep' => ['type' => 'staff', 'permissions' => [ 'view_clients', 'store_clients', 'update_clients', 'view_quotations', 'store_quotations', 'update_quotations', 'view_sales_orders', 'store_sales_orders', 'update_sales_orders', 'view_products', 'view_invoices', ]], 'inventory_manager' => ['type' => 'staff', 'permissions' => [ 'view_products', 'store_products', 'update_products', 'destroy_products', 'view_stock_movements', 'store_stock_movements', 'view_sales_orders', ]], 'project_manager' => ['type' => 'staff', 'permissions' => [ 'view_projects', 'store_projects', 'update_projects', 'view_project_phases', 'update_project_phases', 'view_clients', 'view_quotations', ]], 'finance_officer' => ['type' => 'staff', 'permissions' => [ 'view_invoices', 'store_invoices', 'update_invoices', 'destroy_invoices', 'view_sales_orders', 'view_clients', ]], 'client' => ['type' => 'user', 'permissions' => [ // self-scoped permissions: enforce ownership in policies 'portal_view_own_quotations', 'portal_view_own_orders', 'portal_view_own_invoices', 'portal_view_own_projects', ]], ],
License
MIT. See LICENSE.