Documentation

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.

bash
$ composer require codesuab/laravel-permission
Zero Overrides

Utilizes idempotent ensure() methods without modifying Eloquent's native core.

Team Scoped

Assign roles globally or scoped per tenant/team with automated middleware context resolution.

Gate & Route Hooks

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.

Native Eloquent Contract

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.

Step 1

Install via Composer

terminal
composer require codesuab/laravel-permission
Step 2

Run Automated Installer

Publishes config/permission.php and executes database migrations:

terminal
php artisan permission:install
Step 3

Seed Wildcard Super-Admin

Registers the master * ability and default administrative role:

terminal
php artisan permission:seed
Step 4

Add Traits to User Model

Import HasPermissions and optionally HasTeams in your Authenticatable model:

app/Models/User.php
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:

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

Usage
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:

Pattern: *

Permits unconditional authorization across every gate and check.

$role->allow('*');
Pattern: 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.

config/permission.php
'super_admin' => [
    'enabled' => true,
    'role'    => 'super-admin',
],

# Route Macros & Middleware

Expressive macros registered directly on Laravel's Route facade:

routes/web.php
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:

index / show users.view
create / store users.create
edit / update users.update
destroy users.delete

# 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.

Policy Inference
// 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:

Team Isolation
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:

Role Permission Matrix
Interactive live demo
Permission Super Admin Manager Editor Viewer
users.view
users.create
users.update
users.delete

# Blade Directives

Pre-compiled directives for conditional view rendering:

resources/views/dashboard.blade.php
{{-- 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:

Navigation.tsx
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:

roles
  • id: bigint unsigned (PK)
  • name: varchar(255)
  • slug: varchar(255) [UNIQUE]
  • is_system: boolean
permissions
  • id: bigint unsigned (PK)
  • name: varchar(255)
  • slug: varchar(255) [UNIQUE]
  • group: varchar(255) nullable
teams
  • id: bigint unsigned (PK)
  • name: varchar(255)
  • slug: varchar(255) [UNIQUE]
  • owner_id: foreignId
role_permissions
  • 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:

Cache Key Format
{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.

Interactive Wildcard Sandbox

Simulate the package's exact regex matching engine in real time.

Presets: · ·
Action checked via can() or Gate.
ALLOW — Access Granted
Pattern "users.*" matches "users.view"
regex
Copied to clipboard