Skip to content

dynamik-dev/marque

Repository files navigation

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages