Looking to hire Laravel developers? Try LaraJobs

laravel-policy-engine maintained by dynamik-dev

Description
A Laravel-native scoped permissions package. Composable, contract-driven authorization that integrates with Gates, Policies, middleware, and Blade.
Last update
2026/04/16 20:57 (dev-fix/code-review-findings)
License
Links
Downloads
2

Comments
comments powered by Disqus

Pint Larastan Pest PHPStan Level 9

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

$user->assignRole('admin', scope: $acmeTeam);
$user->assignRole('viewer', 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::role('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.

Marque::boundary('plan::free', ['posts.read', 'comments.read']);
Marque::boundary('plan::pro', ['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

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 StartedInstallation | Seeding permissions and roles

AuthorizationChecking permissions | Roles | Scoped permissions | Deny rules | Boundaries

IntegrationsMiddleware | Blade | Model policies | Sanctum

Policy DocumentsDocument format | Import / Export

ExtendingSwapping implementations | Events | Cache

ReferenceConfiguration | Contracts | Events | Artisan commands | Comparison with Spatie