PHPackages                             damianulan/laravel-sentinel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. damianulan/laravel-sentinel

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

damianulan/laravel-sentinel
===========================

Laravel context-based roles and permissions. Enables assigning roles and permissions in a certain context for ex. a model instance.

1.0.6(1mo ago)0111MITPHPPHP ^8.3CI passing

Since Sep 14Pushed 1mo agoCompare

[ Source](https://github.com/damianulan/laravel-sentinel)[ Packagist](https://packagist.org/packages/damianulan/laravel-sentinel)[ RSS](/packages/damianulan-laravel-sentinel/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (7)Dependencies (12)Versions (9)Used By (1)

Laravel Sentinel
================

[](#laravel-sentinel)

[![Laravel](https://camo.githubusercontent.com/afca9778e22c472c6b601fe4bac81424fb1b47a876c1742009544a6bce5c2e38/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d6164655f776974682d4c61726176656c2d7265643f7374796c653d666f722d7468652d6261646765)](https://laravel.com) [![License](https://camo.githubusercontent.com/7b032738aeb4a8dd7ec78c4402e078ca8c196b0e528c117bbd78088c9a036988/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75653f7374796c653d666f722d7468652d6261646765)](LICENSE)

Laravel Sentinel is a context-aware roles and permissions package for Laravel.

Its core idea is simple:

- roles can be assigned globally or within a specific context
- permissions can be assigned directly to a user
- permissions can also be inherited through roles
- permission checks can optionally receive an Eloquent model instance as context

This makes it useful for applications where a user may have one role in one model context and a different role elsewhere, for example per-project, per-campaign, or per-team access control.

Requirements
------------

[](#requirements)

- PHP `^8.3`
- `illuminate/support` `^9.0|^10.0|^11.0|^12.0`

Installation
------------

[](#installation)

Install the package:

```
composer require damianulan/laravel-sentinel
```

Publish the package assets:

```
php artisan vendor:publish --tag=sentinel
```

Available publish tags:

```
php artisan vendor:publish --tag=sentinel-config
php artisan vendor:publish --tag=sentinel-migrations
php artisan vendor:publish --tag=sentinel-langs
```

Run the published migrations:

```
php artisan migrate
```

Generate the two application warden classes used to define your platform's roles and permissions:

```
php artisan make:roles
php artisan make:permissions
```

Then synchronize those definitions into the database:

```
php artisan sentinel:run
```

Run `sentinel:run` every time you add, remove, or change role and permission definitions.

How Sentinel Works
------------------

[](#how-sentinel-works)

Sentinel has four main layers:

1. Warden classes in `App\Warden` define your canonical roles and permissions as constants and configuration arrays.
2. `sentinel:run` reads those classes and seeds the `roles`, `permissions`, and `roles_permissions` tables.
3. Your authenticatable model uses `Sentinel\Traits\HasRolesAndPermissions`.
4. Runtime checks use direct permissions, inherited role permissions, and optional model context.

Database Structure
------------------

[](#database-structure)

Published migrations create these tables:

- `roles`
- `permissions`
- `roles_permissions`
- `has_roles`
- `has_permissions`

Important pivots:

- `has_permissions` stores direct permission assignments per morphable model
- `has_roles` stores role assignments per morphable model and per morphable context

That means a user can have the same role multiple times, each attached to a different context.

Setup
-----

[](#setup)

### 1. Add the trait to your user model

[](#1-add-the-trait-to-your-user-model)

```
namespace App\Models\Core;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Sentinel\Traits\HasRolesAndPermissions;

class User extends Authenticatable
{
    use HasRolesAndPermissions;
}
```

### 2. Define roles in `App\Warden`

[](#2-define-roles-in-appwarden)

Generated roles classes extend `Sentinel\Config\Warden\RoleWarden`.

Example:

```
namespace App\Warden;

use Sentinel\Config\Warden\RoleWarden;

final class RolesLib extends RoleWarden
{
    public const ROOT = 'root';
    public const ADMIN = 'admin';
    public const PROJECT_MANAGER = 'project-manager';
    public const MEMBER = 'member';

    public static function assignable(): array
    {
        return [
            self::ADMIN,
            self::PROJECT_MANAGER,
            self::MEMBER,
        ];
    }

    public static function admins(): array
    {
        return [
            self::ROOT,
            self::ADMIN,
        ];
    }

    public static function labels(): array
    {
        return [
            self::ROOT => 'Root',
            self::ADMIN => 'Administrator',
            self::PROJECT_MANAGER => 'Project manager',
            self::MEMBER => 'Member',
        ];
    }
}
```

Key methods:

- `values()`: all declared constants plus optional extra items
- `assignable()`: roles allowed for platform assignment flows
- `admins()`: roles treated as admin roles by `isAdmin()`
- `labels()`: optional slug-to-label mapping

### 3. Define permissions in `App\Warden`

[](#3-define-permissions-in-appwarden)

Generated permissions classes extend `Sentinel\Config\Warden\PermissionWarden`.

Example:

```
namespace App\Warden;

use Sentinel\Config\Warden\PermissionWarden;

final class PermissionsLib extends PermissionWarden
{
    public const PROJECT_VIEW = 'project-view';
    public const PROJECT_UPDATE = 'project-update';
    public const PROJECT_DELETE = 'project-delete';
    public const USER_MANAGE = 'user-manage';

    public static function assignable(): array
    {
        return [
            self::PROJECT_VIEW => ['*'],
            self::PROJECT_UPDATE => [RolesLib::PROJECT_MANAGER, RolesLib::ADMIN],
            self::PROJECT_DELETE => ['admins'],
        ];
    }

    public static function nonassignable(): array
    {
        return [
            self::USER_MANAGE => ['admins'],
        ];
    }

    public static function labels(): array
    {
        return [
            self::PROJECT_VIEW => 'View projects',
            self::PROJECT_UPDATE => 'Update projects',
            self::PROJECT_DELETE => 'Delete projects',
            self::USER_MANAGE => 'Manage users',
        ];
    }
}
```

Meaning of the assignment arrays:

- `['*']`: attach permission to every role
- `['admins']`: attach permission to every role returned by `RolesLib::admins()`
- `[RolesLib::ADMIN, RolesLib::MEMBER]`: attach permission only to those explicit roles

Important distinction:

- `assignable()` creates permissions marked as assignable in the database
- `nonassignable()` creates permissions that still exist and can still be checked, but are not meant for regular UI assignment flows

### 4. Seed the definitions

[](#4-seed-the-definitions)

Once your warden classes exist:

```
php artisan sentinel:run
```

What `sentinel:run` does:

- loads the role and permission wardens from `App\Warden`
- creates missing `roles`
- creates missing `permissions`
- attaches permissions to roles
- deletes outdated roles and permissions that no longer exist in the warden classes
- flushes Sentinel's internal cache

Runtime API
-----------

[](#runtime-api)

The main runtime API lives on the model using `HasRolesAndPermissions`.

### Role relationships and assignment

[](#role-relationships-and-assignment)

Roles are assigned either:

- globally through the default context
- specifically to an Eloquent model instance used as the context

Example context model:

```
use App\Models\Project;
use App\Warden\RolesLib;

$user->assignRoleSlug(RolesLib::PROJECT_MANAGER, $project);
$user->assignRoleSlug(RolesLib::ADMIN); // assigned in default context
```

You can also assign by role ID or role model:

```
$user->assignRole($roleId, $project);
$user->assignRole($roleModel, $project);
```

Revoke role assignments:

```
$user->revokeRoleSlug(RolesLib::PROJECT_MANAGER, $project);
$user->revokeRole($roleId, $project);
```

Inspect roles:

```
$user->roles();           // all assigned roles relation
$user->roles($project);   // roles in project context + default system context
$user->roleAssignments(); // raw DB query for has_roles pivot rows

$user->getRoles();        // collection of role slugs
$user->getRolesNames();   // translated or labeled role names

$user->hasRole(RolesLib::ADMIN);
$user->hasRoles([RolesLib::ADMIN, RolesLib::MEMBER]);
$user->hasAnyRoles([RolesLib::ADMIN, RolesLib::PROJECT_MANAGER]);
```

### Direct permissions

[](#direct-permissions)

Direct permissions are stored separately from role-based permissions.

```
use App\Warden\PermissionsLib;

$user->givePermissionsTo(
    PermissionsLib::PROJECT_VIEW,
);

$user->deletePermissions(
    PermissionsLib::PROJECT_VIEW,
);

$user->refreshPermissions(
    PermissionsLib::PROJECT_VIEW,
    PermissionsLib::PROJECT_UPDATE,
);
```

Inspect direct permission assignments:

```
$user->permissions();
$user->hasPermission(Permission::findBySlug(PermissionsLib::PROJECT_VIEW));
```

`hasPermission()` only checks direct permission assignments. It does not resolve inherited permissions or context.

### Permission checks

[](#permission-checks)

Use `hasPermissionTo()` for the normal application-facing check.

It supports:

- direct permissions
- permissions inherited through roles
- optional model context
- wildcard-style prefix matching using `permission-*`
- automatic root bypass

Examples:

```
use App\Warden\PermissionsLib;

$user->hasPermissionTo(PermissionsLib::PROJECT_VIEW);
$user->hasPermissionTo(PermissionsLib::PROJECT_UPDATE, $project);
$user->hasPermissionTo('project-*', $project);
```

Behavior notes:

- If the user has the configured root role, `hasPermissionTo()` always returns `true`
- If a context is provided, Sentinel checks roles attached to that specific context and also roles attached to the default system context
- If no context is provided, Sentinel checks direct permissions and global role-derived permissions

### Admin and root helpers

[](#admin-and-root-helpers)

```
$user->isAdmin();
$user->isRoot();
```

- `isAdmin()` checks any role returned by your `RoleWarden::admins()` definition
- `isRoot()` checks the slug configured at `sentinel.root`

### Query scopes

[](#query-scopes)

The trait also adds scopes:

```
User::query()->withRole(RolesLib::ADMIN)->get();
User::query()->withPermission(PermissionsLib::PROJECT_UPDATE)->get();
```

`withPermission()` matches both direct permissions and permissions inherited through roles.

Contexts
--------

[](#contexts)

Sentinel's distinguishing feature is context-aware role assignment.

The default context class is configured in `config/sentinel.php`:

```
'default_context' => Sentinel\Contexts\System::class,
```

The built-in `System` context implements `Sentinel\Contracts\DefaultContext` and returns key `0`.

This means a role assigned without an explicit model context becomes a system-wide role:

```
$user->assignRoleSlug(RolesLib::ADMIN);
```

To check permissions in a specific context:

```
$user->hasPermissionTo(PermissionsLib::PROJECT_UPDATE, $project);
```

You can replace the default context class in config as long as it implements `Sentinel\Contracts\DefaultContext`.

Gate Integration
----------------

[](#gate-integration)

Sentinel registers a `Gate::before()` callback in its service provider.

That means standard Laravel authorization checks can automatically resolve through `hasPermissionTo()` when the authenticated user exposes that method.

Examples:

```
auth()->user()->can('project-update', $project);
auth()->user()->cannot('project-delete', $project);
```

The first Eloquent model found in the gate arguments is treated as the Sentinel context.

This makes Sentinel compatible with many normal Laravel authorization flows without writing a separate policy for every permission slug.

Blade Directives
----------------

[](#blade-directives)

Sentinel registers three Blade conditionals:

```
@role('admin')
    Visible to admins.
@endrole

@admin
    Visible to admin-class roles.
@endadmin

@root
    Visible only to the configured root role.
@endroot
```

Models
------

[](#models)

### `Sentinel\Models\Role`

[](#sentinelmodelsrole)

Useful methods:

```
use Sentinel\Models\Role;

$role = Role::findBySlug('admin');
$id = Role::getId('admin');
$assignable = Role::whereAssignable()->get();
$select = Role::getSelectList();
```

Relationships:

- `permissions()`

### `Sentinel\Models\Permission`

[](#sentinelmodelspermission)

Useful methods:

```
use Sentinel\Models\Permission;

$permission = Permission::findBySlug('project-update');
$assignable = Permission::whereAssignable()->get();
$select = Permission::getSelectList();
```

Relationships:

- `roles()`

Configuration
-------------

[](#configuration)

Published config file: `config/sentinel.php`

Main options:

```
return [
    'default_context' => Sentinel\Contexts\System::class,

    'models' => [
        'role' => Sentinel\Models\Role::class,
        'permission' => Sentinel\Models\Permission::class,
    ],

    'root' => 'root',

    'cache' => [
        'driver' => 'default',
        'key' => 'sentinel.cache',
        'expire_after' => 86400,
    ],
];
```

What these options control:

- `default_context`: global role context used when no model context is provided
- `models.role` / `models.permission`: override the Eloquent models used by Sentinel
- `root`: the role slug that bypasses all permission checks
- `cache.*`: cache used to store discovered Warden namespaces

Cache Behavior
--------------

[](#cache-behavior)

Sentinel caches the detected warden classes:

- `rolesLib`
- `permissionsLib`

These values are flushed automatically when `sentinel:run` completes.

If you move or rename your `App\Warden` classes and want to clear cache manually, rerun:

```
php artisan sentinel:run
```

Middleware
----------

[](#middleware)

The package includes middleware classes:

- `Sentinel\Http\Middleware\RoleMiddleware`
- `Sentinel\Http\Middleware\PermissionMiddleware`

Behavior:

- `RoleMiddleware` checks `Auth::user()->hasRole($role)`
- `PermissionMiddleware` checks `Auth::user()->cannot($permission, $context)`

Example registration in Laravel 11+:

```
use Sentinel\Http\Middleware\PermissionMiddleware;
use Sentinel\Http\Middleware\RoleMiddleware;

->withMiddleware(function ($middleware): void {
    $middleware->alias([
        'role' => RoleMiddleware::class,
        'permission' => PermissionMiddleware::class,
    ]);
})
```

Example usage:

```
Route::middleware('role:admin')->group(function () {
    // ...
});

Route::middleware('permission:project-update')->group(function () {
    // ...
});
```

If you need context-aware permission middleware, pass the appropriate argument pattern for your application or prefer standard Laravel gate checks inside controllers/services where the actual model instance is available.

Testing Helper
--------------

[](#testing-helper)

The package includes a small testing concern:

```
use Sentinel\Testing\Concerns\InteractsWithRoles;

uses(InteractsWithRoles::class);

it('assigns the admin role', function () use ($user) {
    $this->assertHasRole('admin', $user);
});
```

Typical Example
---------------

[](#typical-example)

A user can be a global admin and also a project-scoped manager:

```
use App\Models\Project;
use App\Warden\PermissionsLib;
use App\Warden\RolesLib;

$project = Project::findOrFail(1);

$user->assignRoleSlug(RolesLib::ADMIN);
$user->assignRoleSlug(RolesLib::PROJECT_MANAGER, $project);

$user->hasPermissionTo(PermissionsLib::PROJECT_DELETE); // true if granted through admin role
$user->hasPermissionTo(PermissionsLib::PROJECT_UPDATE, $project); // true
```

Another project will not automatically inherit the project-scoped role:

```
$otherProject = Project::findOrFail(2);

$user->hasPermissionTo(PermissionsLib::PROJECT_UPDATE, $otherProject); // false unless another role grants it
```

Warden Discovery Rules
----------------------

[](#warden-discovery-rules)

Sentinel looks for role and permission wardens under `App\Warden` by scanning Composer's autoload classmap and finding concrete subclasses of:

- `Sentinel\Config\Warden\RoleWarden`
- `Sentinel\Config\Warden\PermissionWarden`

If Sentinel cannot find one of these classes, it throws:

- `Role Warden class is not declared in the project!`
- `Permission Warden class is not declared in the project!`

If class discovery seems stale after adding or renaming warden classes, refresh Composer autoload metadata:

```
composer dump-autoload
php artisan sentinel:run
```

Caveats
-------

[](#caveats)

- Context applies to roles, not to direct permissions
- `hasPermission()` checks only direct permissions; use `hasPermissionTo()` for normal authorization decisions
- `sentinel:run` removes database roles and permissions that are no longer present in your warden definitions
- Middleware can only be context-aware when the relevant model instance is actually available to the middleware

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

Contact
-------

[](#contact)

Questions and contributions: `damian.ulan@protonmail.com`

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance90

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~26 days

Recently: every ~10 days

Total

8

Last Release

52d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8edae7c1a9d4cc850ce273f54ba3ac7ee43cd145aebab25b23247be9309a5ab1?d=identicon)[damianulan](/maintainers/damianulan)

---

Top Contributors

[![damianulan](https://avatars.githubusercontent.com/u/31820473?v=4)](https://github.com/damianulan "damianulan (58 commits)")

---

Tags

laravelrolespermissionssentinel

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/damianulan-laravel-sentinel/health.svg)

```
[![Health](https://phpackages.com/badges/damianulan-laravel-sentinel/health.svg)](https://phpackages.com/packages/damianulan-laravel-sentinel)
```

###  Alternatives

[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[shanmuga/laravel-entrust

This package provides a flexible solution to add ACL to Laravel

68312.9k2](/packages/shanmuga-laravel-entrust)[hasinhayder/tyro

Tyro - The ultimate Authentication, Authorization, and Role &amp; Privilege Management solution for Laravel 12 &amp; 13

6712.1k2](/packages/hasinhayder-tyro)[beatswitch/lock-laravel

A Laravel Driver for Lock.

15529.1k1](/packages/beatswitch-lock-laravel)[pingpong/trusty

Roles and Permissions for Laravel 4

2680.0k9](/packages/pingpong-trusty)[phpzen/laravel-rbac

Role based access control for Laravel 5

383.2k](/packages/phpzen-laravel-rbac)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
