Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Defender Laravel Package

artesaos/defender

Artesaos Defender is a Laravel ACL package for role- and permission-based access control. It provides middleware, Blade directives, caching, and a simple API for assigning roles and abilities to users so you can protect routes and UI consistently.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish in one pass, then inspect three files before writing any code:

composer require artesaos/defender
php artisan vendor:publish --provider="Artesaos\Defender\Providers\DefenderServiceProvider"
php artisan migrate

If the provider string doesn't resolve (it moved between releases), run php artisan vendor:publish interactively and pick the Defender entry, or check vendor/artesaos/defender/composer.json for extra.laravel.providers.

Where to look first

  1. config/defender.php — model bindings (role, permission, user), pivot table names, and the pretend block. This is the file you will actually edit in a real project.
  2. vendor/artesaos/defender/src/Traits/ — the traits you put on your User (DefenderRoleTrait) and on any custom Role model (DefenderPermissionTrait).
  3. vendor/artesaos/defender/src/DefenderManager.php — the object behind the Defender facade / defender() helper. Read the public methods; that's your real API surface.
  4. vendor/artesaos/defender/database/migrations/ — confirms the user_roles and permission_role schema you'll be querying directly from time to time.

Wire the trait onto your user model

use Artesaos\Defender\Traits\DefenderRoleTrait;

class User extends Authenticatable
{
    use DefenderRoleTrait;
}

First use case: gate an admin area. Seed one role and one permission, attach them, then check on the user:

$role = Role::firstOrCreate(['name' => 'admin'], ['readable_name' => 'Administrator']);
$perm = Permission::firstOrCreate(['name' => 'dashboard.view'], ['readable_name' => 'View dashboard']);

$role->attachPermission($perm);
$user->attachRole($role);

// anywhere with an authenticated user
if ($user->canDo('dashboard.view')) { /* ... */ }

Resolve Role/Permission through config('defender.model.role') rather than importing the concrete class if you plan to swap in your own models later.

Implementation Patterns

Seed roles/permissions as data, not code. A dedicated seeder using firstOrCreate keyed on name makes re-runs safe and lets you add readable_name for UI display without touching checks. Never create permissions lazily inside controllers — slugs drift and you end up with edit-post and posts.edit both in production.

Centralise slugs. canDo() returns false on a typo, silently. Use class constants or an enum (Permission::EDIT_POST = 'posts.edit') so a misspelled ability fails at review time, not at 3am.

Blade directives for UI, checks for enforcement. The package registers @canDo / @endCanDo, @canDoRole / @endCanDoRole, and @isRole / @endIsRole. Use them for showing/hiding buttons, but always re-check server-side in the controller or a form request:

public function authorize(): bool
{
    return $this->user()->canDo('posts.edit');
}

Bridge to Laravel's Gate so @can, policies, and Gate::authorize() all see Defender permissions. Register once in AuthServiceProvider::boot():

Gate::before(function ($user, $ability) {
    if (method_exists($user, 'canDo')) {
        return $user->canDo($ability) ? true : null; // null = abstain
    }
});

Returning null (not false) when the check fails is important — it lets policies and other gates still run. This is the single highest-leverage integration: after it, @can('posts.edit') and $this->authorize('posts.edit') just work.

Super-admin shortcut is the same hook with a role check, but scope it tightly:

Gate::before(fn ($user, $ability) => $user->isRole('super-admin') ? true : null);

Route middleware for coarse grouping, if your version ships NeedsRole / NeedsPermission (they are sometimes left commented out in the service provider — uncomment and register the aliases yourself):

Route::middleware('needsPermission:posts.edit')->group(fn () => Route::resource('posts', PostController::class));

Eager load roles and their permissions on list pages and dashboards: User::with('roles.permissions')->paginate(). Without it, canDo() inside a Blade loop fires a query per row — the classic N+1 in this package.

Testing. RefreshDatabase + a seeder, then actingAs($user). Note that Defender::canDo('x') without an argument reads the current auth user; in queued jobs and console commands there is no auth user, so pass the user explicitly or check on $user->canDo(...) directly.

Gotchas and Tips

  • Not Gate-native. Out of the box this is a parallel ACL: @can and $this->authorize() know nothing about Defender permissions. The Gate::before bridge above is effectively required in any Laravel ≥ 5.5 app.
  • Facade with no user fails closed. Defender::canDo('posts.edit') in a console command, queue job, or before Auth::login() returns false — not an error. If a background job behaves as if the user has no permissions, this is why.
  • pretend mode. config/defender.php has a pretend block that lets listed users bypass checks. Verify it is enabled => false in every non-local environment and treat it as a deploy checklist item.
  • Stale role checks. Roles/permissions are loaded per request and cached on the user instance; after attachRole() in the same request, the trait's already-resolved relation may not reflect the change. $user->load('roles.permissions') or re-fetch the user before asserting in tests.
  • Pivot names are configurable but not migratable after the fact. If you change user_roles / permission_role in config, you must edit the published migration, or the queries break at runtime with a confusing "table not found" error rather than at boot.
  • Orphaned pivots on delete. Deleting a role does not cascade to its permission links unless your migration added FK cascades. Add onDelete('cascade') when you publish, or clean up in the deleting model's deleting event.
  • canDo vs canDoRole. canDo is "user has this permission through any role"; canDoRole($role, $perm) scopes the check to one role. Reach for the second only when a user legitimately holds both a broad and a narrow role and you need the narrower test.
  • Version drift on the API. The trait namespace (Traits\DefenderRoleTrait vs older root-level placement), provider FQCN, and middleware aliases have all shifted across releases. Trust vendor/ over README snippets — the README lags the tagged releases.
  • Laravel compatibility ceiling. v0.12.0 adds Laravel 9 support, so illuminate/* constraints now clear L9. Laravel 10/11 is still unclaimed — check the constraints in composer.json before assuming it works. If it's blocked, a thin fork bumping constraints usually works because the package is small, but budget for that. Note the gap pattern: the previous release was March 2022 (L8 era) and L9 support arrived only in this release, so expect support to land a major version behind. Fine for an existing codebase, a deliberate choice for a new one.
  • Upgrading to v0.12.0 changes little in your code. It's a constraint bump rather than an API change, so the trait/provider/signature surface you relied on in L8 stays put. If you're moving an app from L8 to L9, this is the version that clears the composer update blocker — lock it alongside the framework bump and re-run your permission seeder plus the auth-related feature tests.
  • Naming convention pays off. resource.action slugs (posts.edit, billing.view) map cleanly onto both Defender permissions and future Gate abilities, so the bridge keeps working if you ever migrate to policy-only authorization.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource