Checking access
Once schema, resolver, and rules are in place, you never touch the compiler directly. You ask questions through the model, query scopes, static helpers, or middleware.
Set up the model
Section titled “Set up the model”Add the HasWarrantSchema trait and point it at the schema:
use Illuminate\Database\Eloquent\Model;use Warrant\HasWarrantSchema;
class Document extends Model{ use HasWarrantSchema;
public function warrantSchema(): string { return \App\Warrant\DocumentSchema::class; }}Boolean checks
Section titled “Boolean checks”Run as a scoped EXISTS query — no records are loaded:
Document::userHasAbilities('update', $document); // a model instanceDocument::userHasAbilities('update', $documentId); // a keyDocument::userHasAbilities(['view', 'update'], $document); // several at onceDocument::userHasAbilities('create'); // no-target / capability
// Instance form (checks $this):$document->hasAbility('update');Each accepts an optional $user (defaults to auth()->user()) and, for
userHasAbilities, an AbilityMatchMode.
Filtering queries
Section titled “Filtering queries”The hasAbility scope restricts a query to the rows the user may act on — the
“which records?” question, answered in SQL:
// Documents the current user can update:Document::query()->hasAbility('update')->paginate();
// Rows they can BOTH view and approve:Document::query()->hasAbility(['view', 'approve'], matchMode: AbilityMatchMode::ALL)->get();
// For a specific user:Document::query()->hasAbility('delete', $user)->get();Per-row abilities
Section titled “Per-row abilities”The selectAbilities scope attaches a computed abilities column — a JSON array
of what the user can do to that row — so your UI renders controls without N
extra checks:
$rows = Document::query()->selectAbilities()->get();
$rows->first()->abilities; // ['view', 'update']The list is ordered by ability declaration order in the schema.
On a list endpoint you often only care about a subset (say, just update for an
Edit button). Narrowing it is a real saving — the attached subquery grows one
UNION ALL branch per ability:
Document::query()->selectAbilities(onlyAbilities: ['update'])->get();You can also change the column name and attach abilities to an already-loaded model:
Document::query()->selectAbilities(selectedAbilitiesKey: 'perms')->get();
$document->loadAbilities(); // sets $document->abilities$document->getUserAbilities($document); // ['view', 'update'] — static formMatch modes
Section titled “Match modes”When you check several abilities at once, AbilityMatchMode decides how they
combine:
AbilityMatchMode::ALL— the row/user must satisfy every listed ability.AbilityMatchMode::ANY— any one is enough.
use Warrant\AbilityMatchMode;
Document::query()->hasAbility(['view', 'approve'], matchMode: AbilityMatchMode::ANY)->get();Capability (no-target) checks
Section titled “Capability (no-target) checks”Not every check is about a row. “Can this user create documents?” or “can they
access settings?” have no target. Pass null as the target (or omit it):
Document::userHasAbilities('create'); // target defaults to nullDocumentSchema::getUserAbilities(); // all no-target abilitiesFor section-level capabilities with no model at all, define a
capability schema with
const model = '' and only #[GlobalCondition] conditions. In a no-target check,
targeted conditions are treated as false, so only global logic contributes.
Passing context
Section titled “Passing context”Every check API takes an optional context: array — values for any @context
keys the rules reference. See Check-time context.
API summary
Section titled “API summary”| Call | Question |
|---|---|
Model::userHasAbilities($abilities, $target, $user, $matchMode, $context) |
Can they? (bool) |
$model->hasAbility($abilities, $user, $matchMode, $context) |
Can they, for this instance? |
Model::getUserAbilities($target, $user, $context) |
What can they do to this? (array) |
->hasAbility(...) scope |
Which rows? |
->selectAbilities(...) scope |
What per row? |
$model->loadAbilities(...) |
Attach abilities to an instance |
Model::authorize($abilities, $target, $user, $matchMode, $context) |
Can they? — throws a 403 with a message |
Model::userCouldEverHave($abilities, $user, $matchMode) |
Could they ever? (bool) |
Full signatures are in the Checking API reference.
Two questions this guide doesn’t cover live on their own pages: when a denial
should explain itself, authorize throws a 403
carrying the responsible rule’s message; and when you only need to know whether an
ability is conceivable — to render a nav or gate a section without a query —
reachability answers structurally, no SQL.
