patrickhanna/laravel-warden

Schema-based authorization and permissions for Laravel, with database-scoped ability checks.

Maintainers

Package info

github.com/patrickjames242/laravel-warrant

pkg:composer/patrickhanna/laravel-warden

Transparency log

Statistics

Installs: 62

Dependents: 0

Suggesters: 0

Stars: 10

Open Issues: 0

v3.3.0 2026-08-18 16:27 UTC

This package is auto-updated.

Last update: 2026-08-22 00:37:25 UTC


README

Laravel Warrant

Laravel Warrant

Row-level permissions & authorization for Laravel — one rule, compiled straight to SQL.

Beta Documentation Laravel 11 & 12 PHP 8.2+ MIT License

Read the documentation → laravel-warrant.dev

Warning

Laravel Warrant is in beta and still being tested. It's usable, but the API may change between releases — pin your version and check the changelog before upgrading. Please report any issues you run into.

Note

Not an official Laravel package. Laravel Warrant is an independent, open-source project. It is not affiliated with, maintained by, or endorsed by the Laravel team.

Schema-based authorization for Laravel that compiles a small, human-readable rule language directly into SQL — so "what can this user do?" and "which rows can this user touch?" are answered by the database in a single query, not by loading records into memory.

if is_self or (is_manager and same_department)
they can view, update
they cannot delete

That block is a real, complete Warrant rule. Warrant turns it into a WHERE clause.

Installation

composer require patrickhanna/laravel-warrant

The service provider auto-registers. Publish the config to edit it in place:

php artisan vendor:publish --tag=warrant-config

Requirements: PHP 8.2+, Laravel 11 or 12. The SQL Warrant generates is supported on PostgreSQL, MySQL/MariaDB, and SQLite.

Documentation

Full docs live at laravel-warrant.dev:

Why Warrant

  • One source of truth. The same rule set answers a single check, filters a list, and reports the per-row ability list — no permission logic duplicated between a policy and a query.
  • Compiles to SQL. Rules never run in PHP or pull your models into memory; they become a WHERE clause the database evaluates.
  • Rules are data. Store them in the database, generate them from a GUI, or hard-code them — you decide where they come from and change them without touching app code.
  • Readable language. Rules are written in a small if … they can/cannot … language that non-authors can follow.
  • Integrates with Laravel's Gate. $user->can(), @can, Gate::authorize, and the can: route middleware resolve Warrant abilities, while abilities no schema declares fall through to your existing policies.

See Why Warrant for the full rationale and a comparison with the policy/query approach.

A quick example

1. The model uses the trait and names its schema:

use Illuminate\Database\Eloquent\Model;
use Warrant\HasWarrantSchema;

class Timesheet extends Model
{
    use HasWarrantSchema;

    public function warrantSchema(): string
    {
        return \App\Warrant\TimesheetSchema::class;
    }
}

2. The schema declares the vocabulary — abilities and conditions:

namespace App\Warrant;

use App\Models\Timesheet;
use Illuminate\Contracts\Database\Query\Builder;
use Warrant\Ability;
use Warrant\GlobalCondition;
use Warrant\Schema\Conditions\GlobalConditionContext;
use Warrant\Schema\Conditions\TargetedConditionContext;
use Warrant\Schema\WarrantSchema;
use Warrant\TargetedCondition;

class TimesheetSchema extends WarrantSchema
{
    public const model = Timesheet::class;

    #[Ability] public const VIEW    = 'view';
    #[Ability] public const UPDATE  = 'update';
    #[Ability] public const DELETE  = 'delete';
    #[Ability] public const APPROVE = 'approve';

    // Targeted: narrows WHICH timesheet rows the user matches.
    #[TargetedCondition]
    public function isSelf(TargetedConditionContext $c): Builder
    {
        return $c->query->whereRaw('timesheets.user_id = ?', [$c->user->getAuthIdentifier()]);
    }

    #[TargetedCondition]
    public function inDepartment(TargetedConditionContext $c): Builder
    {
        return $c->query->whereIn('timesheets.department_id', $c->arguments);
    }

    // Global: a plain yes/no about the user, independent of any row.
    #[GlobalCondition]
    public function isAdmin(GlobalConditionContext $c): bool
    {
        return (bool) $c->user->is_admin;
    }
}

3. The resolver hands rules (as data) to Warrant for the current user:

namespace App\Warrant;

use Warrant\RuleResolutionContext;
use Warrant\RuleResolver;
use Warrant\RuleSyntaxTree\WarrantRuleSet;

class DatabaseRuleResolver implements RuleResolver
{
    public function resolve(RuleResolutionContext $context): WarrantRuleSet
    {
        // Load the raw rule string + any binding values for this user/resource.
        [$syntax, $bindings] = MyRuleStore::for(
            user: $context->user,
            resource: $context->schemaKey, // 'timesheets'
        );

        return WarrantRuleSet::fromSyntax($context->schemaKey, $syntax, $bindings);
    }
}

The rules themselves are just text:

if is_self they can view, update, delete
if in_department(?, ?) they can view, approve
if is_admin they can *

4. Register the resolver and schema in config/warrant.php:

'rule_resolver' => App\Warrant\DatabaseRuleResolver::class,
'schemas' => [App\Warrant\TimesheetSchema::class],

5. Check access — every call is a single SQL query:

// Which timesheets can the current user update?
$editable = Timesheet::query()->userHasAbility('update')->get();

// Can this user approve this specific timesheet?
if (Timesheet::userHasAbilities('approve', $timesheet)) { /* ... */ }

// Attach the per-row ability list, e.g. for rendering buttons.
$rows = Timesheet::query()->selectUserAbilities()->get();
$rows->first()->abilities; // e.g. ['view', 'update']

// Or go through Laravel's Gate — Warrant resolves these too:
$user->can('approve', $timesheet);

The Quick start walks through this end to end.

License

Laravel Warrant is open-source software licensed under the MIT license.