Laravel Permission
An enterprise-grade, framework-native authorization package for Laravel. Combines Role-Based Access Control (RBAC) with fine-grained direct permissions, multi-tenant team boundaries, and automatic front-to-back reactivity.
composer require codesuab/laravel-permission
Utilizes idempotent
ensure() methods
without modifying Eloquent's native core.
Assign roles globally or scoped per tenant/team with automated middleware context resolution.
Automatic Gate::before
integration, model table inference, and fluent macros.
# Architecture & Core Principles
This package is designed to eliminate N+1 bottlenecks and boilerplate code. It aligns with standard Laravel conventions rather than introducing divergent paradigms or custom query builders.
Unlike packages that override core Eloquent lifecycle hooks, this package uses clean relationships and contextually caches resolved abilities under your application's configured cache store.
# Installation & Setup
Follow these standard steps to publish configuration, run database migrations, and equip your user model.
Install via Composer
composer require codesuab/laravel-permission
Run Automated Installer
Publishes config/permission.php
and executes database migrations:
php artisan permission:install
Seed Wildcard Super-Admin
Registers the master * ability and default
administrative role:
php artisan permission:seed
Add Traits to User Model
Import HasPermissions and
optionally HasTeams in
your Authenticatable model:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Permission\Traits\HasPermissions;
use Permission\Traits\HasTeams;
class User extends Authenticatable
{
use HasPermissions, HasTeams;
// ...
}
# Configuration Reference
The configuration file is published at config/permission.php:
return [
/*
* The User Eloquent model to associate roles and permissions with.
*/
'user_model' => env('PERMISSION_USER_MODEL', App\Models\User::class),
/*
* Eloquent models used across the package.
*/
'models' => [
'role' => Permission\Models\Role::class,
'permission' => Permission\Models\Permission::class,
'team' => Permission\Models\Team::class,
],
/*
* Database table names.
*/
'tables' => [
'roles' => 'roles',
'permissions' => 'permissions',
'role_permissions' => 'role_permissions',
'user_roles' => 'user_roles',
'user_permissions' => 'user_permissions',
'teams' => 'teams',
'team_users' => 'team_users',
],
/*
* Super admin bypass settings. Users with this role bypass all checks.
*/
'super_admin' => [
'enabled' => true,
'role' => 'super-admin',
],
/*
* Cache configuration for resolved abilities.
*/
'cache' => [
'enabled' => true,
'store' => null, // defaults to Cache::getDefaultDriver()
'ttl' => 3600, // seconds
'prefix' => 'app.permission',
],
/*
* Multi-tenancy and team settings.
*/
'teams' => [
'enabled' => true,
'route_parameter' => 'team',
'require_membership' => true,
'current_team_attribute' => 'current_team_id',
'global_permissions' => true,
],
];
# User Model & Traits API
The HasPermissions trait
equips the authenticatable model with fluent role and permission management:
| Method | Signature | Description |
|---|---|---|
| assignRole() | string|array $roles, $teamId = null | Assigns one or more roles without detaching existing. |
| syncRoles() | array $roles, $teamId = null | Synchronizes roles, detaching unlisted assignments. |
| removeRole() | string|array $roles, $teamId = null | Detaches specified roles. |
| hasRole() | string $role | Determines if the user holds the given role. |
| givePermissionTo() | string|array $perms, $teamId = null | Grants direct ACL permissions to user. |
| revokePermissionTo() | string|array $perms, $teamId = null | Revokes direct ACL permissions. |
| hasPermission() | string $permission | Checks permission via role hierarchy or direct grant. |
| permissionSlugs() | void | Returns list of all resolved permissions for the user. |
# Permissions & Roles Fluent API
use Permission\Models\Permission;
use Permission\Models\Role;
// 1. Ensure permissions exist idempotently
Permission::ensure('users.view');
Permission::ensureMany(['users.create', 'users.update', 'users.delete']);
// 2. Generate standard resource CRUD in bulk
// ['view' => 'posts.view', 'create' => 'posts.create', 'update' => 'posts.update', 'delete' => 'posts.delete']
$crud = Permission::crud('posts');
// 3. Define Roles and assign permissions
$editor = Role::createRole('editor');
$editor->allow(['posts.view', 'posts.create', 'posts.update']);
// 4. Assign to User
$user->assignRole('editor');
// 5. Check permissions
if ($user->hasPermission('posts.update')) {
// Authorized
}
# Wildcard Permissions
Dot-notation wildcards allow authorization across entire modules or subdomains:
*
Permits unconditional authorization across every gate and check.
$role->allow('*');
users.*
Matches any ability under users (users.edit,
users.delete).
$role->allow('users.*');
# Super Admin Bypass
Users holding the configured super-admin role short-circuit
checks and evaluate immediately to true
across Gates, Policies, and Middleware.
'super_admin' => [
'enabled' => true,
'role' => 'super-admin',
],
# Route Macros & Middleware
Expressive macros registered directly on Laravel's Route facade:
use Illuminate\Support\Facades\Route;
// Require ANY listed permission (OR)
Route::get('/users', [UserController::class, 'index'])->can('users.view');
// Require ALL listed permissions (AND)
Route::get('/export', [ExportController::class, 'run'])->canAll('reports.view', 'reports.export');
// Role guards
Route::get('/admin', [AdminController::class, 'index'])->role('admin');
Route::get('/audit', [SecurityController::class, 'index'])->roleAll('admin', 'auditor');
// Auto-bind Resource controller CRUD
Route::resource('billing', BillingController::class)->can('billing');
# Resource Controller Mapping
Chaining can('resource') maps
standard controller methods automatically:
# Gate & Policy Integration
The package registers a non-intrusive Gate::before hook. If an ability
check matches an existing package permission, it is resolved immediately; otherwise, it returns
null, allowing execution to proceed down to your
custom Laravel Policies.
// 1. Direct permission checks
Gate::allows('users.update');
$this->authorize('users.update');
// 2. Eloquent Model inference
// Automatically infers resource from $model->getTable()
$post = Post::findOrFail($id);
$this->authorize('update', $post); // Checks 'posts.update'
$this->authorize('delete', $post); // Checks 'posts.delete'
# Teams & Multi-Tenancy
Users can hold distinct roles across multiple tenant teams. Assign abilities globally or strictly scoped to a team ID:
use Permission\Models\Team;
$teamA = Team::createTeam('Acme North');
$teamB = Team::createTeam('Acme South');
// Assign role scoped specifically to Team A
$user->assignRole('manager', teamId: $teamA->id);
// Checking access scoped to team context
app('permission')->team($teamA->id)->check($user, 'invoices.refund'); // true
app('permission')->team($teamB->id)->check($user, 'invoices.refund'); // false
# ACL Matrix Service & Preview
The AclMatrix service exports
role-permission matrices without manual pivot queries:
| Permission | Super Admin | Manager | Editor | Viewer |
|---|---|---|---|---|
| users.view | ||||
| users.create | ||||
| users.update | ||||
| users.delete |
# Blade Directives
Pre-compiled directives for conditional view rendering:
{{-- Check single ability --}}
@permission('users.create')
<button class="btn">Create User</button>
@endpermission
{{-- Check any listed permission (OR) --}}
@anypermission('reports.view', 'analytics.view')
<a href="/reports">Reports Center</a>
@endanypermission
{{-- Check all listed permissions (AND) --}}
@allpermissions('billing.view', 'billing.export')
<button>Download Statements</button>
@endallpermissions
{{-- Role checks --}}
@role('admin')
<div>Super Admin Console</div>
@endrole
# Inertia React SDK
When Inertia is present, permissions and roles are automatically shared with the frontend on every request:
import React from 'react';
import { Can, CanAny, Role, usePermissions } from '@permission/react';
export function Navigation() {
const { can, hasRole } = usePermissions();
return (
<nav className="flex items-center gap-4">
{/* Conditional wrapper */}
<Can permission="users.create" fallback={<span>Upgrade plan</span>}>
<button>Invite Teammate</button>
</Can>
{/* Direct hook evaluation */}
{can('invoices.refund') && <button>Issue Refund</button>}
<Role role="admin">
<a href="/admin/settings">Admin Console</a>
</Role>
</nav>
);
}
# Artisan CLI Commands
permission:install
Publishes configuration file and runs initial database migrations.
permission:seed
Seeds wildcard `*` ability and assigns it to the `super-admin` role.
permission:make {res} --crud
Scaffolds view, create, update, and delete actions for a resource.
permission:sync-routes
Inspects application routing table and generates missing permissions.
# Global Helper Functions
Composer automatically autoloads helper functions in src/helpers.php:
| Helper | Return | Description |
|---|---|---|
| permission($name) | bool | Checks permission for currently authenticated user. |
| role($name) | bool | Checks if current user possesses role. |
| team_permission($teamId, $name) | bool | Checks permission scoped to a specific team ID. |
| authorize_permission($name) | void | Throws PermissionDeniedException if
unauthorized. |
# Database Schema
Normalized tables optimized with composite indexes for sub-millisecond evaluation:
- id: bigint unsigned (PK)
- name: varchar(255)
- slug: varchar(255) [UNIQUE]
- is_system: boolean
- id: bigint unsigned (PK)
- name: varchar(255)
- slug: varchar(255) [UNIQUE]
- group: varchar(255) nullable
- id: bigint unsigned (PK)
- name: varchar(255)
- slug: varchar(255) [UNIQUE]
- owner_id: foreignId
- role_id + permission_id
- team_id: foreignId nullable
- [INDEX: role_id, team_id]
# Caching & Invalidation
All resolved abilities are cached contextually to eliminate duplicate database queries:
{prefix}:user:{user_id}:team:{team_id|global}
Example: app.permission:user:14:team:2
Automatic Invalidation: Changes to roles, permissions, or assignments immediately evict corresponding cache tags for affected users.
Simulate the package's exact regex matching engine in real time.
can() or Gate.