dynamik-dev / laravel-policy-engine
A Laravel-native scoped permissions package. Composable, contract-driven authorization that integrates with Gates, Policies, middleware, and Blade.
Requires
- php: ^8.4
- illuminate/contracts: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.27
- laravel/sanctum: ^4.3
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-mutate: ^4.0
This package is auto-updated.
Last update: 2026-04-16 18:57:54 UTC
README
A letter of marque was a document issued by a government that turned pirates into privateers, granting them scoped permission to plunder in specific waters. This package does the same thing for Laravel (minus the plundering). A user can be an admin in one team and a viewer in another. Deny rules, permission boundaries, and JSON policy documents are built in. The whole thing plugs into Laravel's Gate.
composer require dynamik-dev/marque
Quick look
Marque::createRole('admin', 'Admin') ->grant(['members.*', 'posts.*']) ->assignTo($user, scope: $acmeTeam); Marque::createRole('viewer', 'Viewer') ->grant(['posts.read']) ->assignTo($user, scope: $widgetTeam); $user->can('members.remove', $acmeTeam); // true $user->can('members.remove', $widgetTeam); // false
Roles, boundaries, and deny rules can live in JSON files you import at deploy time:
{
"roles": [
{
"id": "editor",
"permissions": ["posts.*", "comments.create", "!posts.delete"]
}
],
"boundaries": [
{ "scope": "plan::free", "max_permissions": ["posts.read", "comments.read"] },
{ "scope": "plan::pro", "max_permissions": ["posts.*", "comments.*", "analytics.*"] }
]
}
php artisan marque:import policies/production.json
Features
Wired into the Gate
$user->can(), @can, $this->authorize(), and can: middleware all work without any extra wiring.
$user->assignRole('editor', $acmeOrg); $user->can('posts.create', $acmeOrg); // true Route::middleware('can:posts.create')->post('/posts', [PostController::class, 'store']);
@can('posts.create', $team) <button>New Post</button> @endcan
Deny rules
Prefix any permission with !. The denial overrides every other role that grants it.
Marque::createRole('editor', 'Editor') ->grant(['posts.*', 'comments.*']) ->deny(['posts.delete']); $editor->can('posts.create'); // true $editor->can('posts.delete'); // false -- deny wins
Permission boundaries
Boundaries set a ceiling on what any role can do inside a scope. A user with admin in a free-tier org still can't access pro-tier features. Pass a scope string or any Scopeable model.
Marque::boundary($freeOrg)->permits(['posts.read', 'comments.read']); Marque::boundary($proOrg)->permits(['posts.*', 'comments.*', 'analytics.*']); $user->assignRole('admin', $freeOrg); $user->can('analytics.view', $freeOrg); // false -- boundary blocks it $user->can('analytics.view', $proOrg); // true
Wildcards
'posts.*' // all post actions '*.read' // read anything '*.*' // superadmin 'posts.update.own' // fine-grained qualifiers
Resource policies
Attach authorization rules directly to a resource type. The when() closure receives the user and the resource instance.
Marque::resource(Post::class) ->allow('update') ->when(fn ($user, $post) => $post->author_id === $user->id); Marque::resource(Post::class) ->deny('delete');
Contract-driven
Every component implements a PHP interface. You can swap any implementation through the service container. See Swapping implementations.
Why not Spatie?
Spatie laravel-permission works well for flat RBAC. Marque adds scoped roles, deny rules, permission boundaries, and declarative policy documents. See the full comparison.
Requirements
| Dependency | Supported Versions |
|---|---|
| PHP | 8.4, 8.5 |
| Laravel | 12, 13 |
| PostgreSQL | 17+ |
| SQLite | 3.35+ |
| Valkey / Redis | 8+ |
SQLite works out of the box for development. PostgreSQL and Valkey are optional — the package tests against both in CI. MySQL is not officially supported but should work fine since Laravel's query builder abstracts the differences.
Documentation
Getting Started — How authorization works | Installation | Permission naming | Seeding permissions and roles
Authorization — Checking permissions | Roles | Scoped permissions | Deny rules | Boundaries | Resource policies
Integrations — Middleware | Blade | Model policies | Sanctum
Policy Documents — Document format | Import / Export
Extending — Swapping implementations | Events | Cache
Testing — Testing authorization
Reference — Configuration | Contracts | Facade | Events | Artisan commands | Comparison with Spatie
