Route middleware
Warrant registers a warrant route middleware. You rarely write the raw string
yourself — build it with Warrant\WarrantMiddleware.
Capability (no-target) checks
Section titled “Capability (no-target) checks”Gate a route by schema key — no model involved:
use Warrant\WarrantMiddleware;
Route::post('/documents', [DocumentController::class, 'store']) ->middleware(WarrantMiddleware::canCreate('documents'));Under the hood canCreate('documents') produces the middleware string
warrant:documents,create.
Targeted checks
Section titled “Targeted checks”Gate by a route-model-bound parameter. The parameter must resolve to a model instance; Warrant finds its schema from the model’s class:
Route::get('/documents/{document}', [DocumentController::class, 'show']) ->middleware(WarrantMiddleware::string('document', 'view'));Standard-ability helpers
Section titled “Standard-ability helpers”Shortcuts for the common verbs. Each takes the target and an optional route-group closure:
WarrantMiddleware::canView('documents');WarrantMiddleware::canCreate('documents');WarrantMiddleware::canUpdate('documents');WarrantMiddleware::canDelete('documents');WarrantMiddleware::canArchive('documents');WarrantMiddleware::canManage('documents'); // uses the literal ability 'manage'Guarding a route group
Section titled “Guarding a route group”guard() (and the helper closures) wrap a group of routes in one check:
WarrantMiddleware::guard('documents', 'view', function () { Route::get('/documents', [DocumentController::class, 'index']); Route::get('/documents/{document}', [DocumentController::class, 'show']);});
// Same, via a helper:WarrantMiddleware::canView('documents', function () { Route::get('/documents', [DocumentController::class, 'index']);});Match modes
Section titled “Match modes”string() and guard() accept an AbilityMatchMode. The mode segment is only
added to the middleware string when it isn’t the default ALL:
use Warrant\AbilityMatchMode;
WarrantMiddleware::string('document', ['view', 'approve'], AbilityMatchMode::ANY);// -> warrant:document,any,view,approveReachability guards
Section titled “Reachability guards”Alongside the row-level checks, the reachability questions have matching guards — gate a section by whether the user could ever act, or short-circuit a route to those who provably never can:
// Reachable only if the user could ever view a document — otherwise 403:Route::get('/documents', ...)->middleware(WarrantMiddleware::couldEver('documents', 'view'));
// Only when the ability is guaranteed by the rules' shape:WarrantMiddleware::always('documents', 'create', fn () => Route::post('/documents', ...));
// Only when the user provably never can (e.g. an upsell page):Route::get('/upgrade', ...)->middleware(WarrantMiddleware::never('documents', 'approve'));These are target-free — the first argument is always a schema key (or a
schema/model class), never a route parameter, because reachability has no row to
bind. Unlike warrant:, the mode and match mode live in the middleware alias
rather than the parameters, so everything after the colon is just the schema key
and abilities (and an ability may safely be named any or all):
warrant.could-ever:documents,view warrant.could-ever.any:documents,view,approvewarrant.always:documents,view warrant.always.any:documents,view,approvewarrant.never:documents,view warrant.never.any:documents,view,approveHow resolution works
Section titled “How resolution works”When the middleware runs, it:
- Tries to resolve the target as a schema key (capability / no-model path).
- If that fails, treats the target as a route parameter name, resolves it to a model, and finds the schema from the model’s class.
- Parses the segment after the target:
all/anybecomes the match mode; anything else is the first ability. - Calls
authorize— the throwing check — aborting 403 when the user is unauthenticated or lacks the abilities. Because it goes throughauthorize, a denial on a model-bound route carries the responsible rule’s denial message instead of a bare status.
