Skip to content

Comparison

Checked on 2026-10-28 against these exact versions. Every cell links to the doc or source line it is based on. ”—” means the feature does not exist in that version (searched in docs and src/).

Version checked
laravel-access-profiles0.1.0 (Packagist)
spatie/laravel-permission8.3.2 (current major 8) (Packagist)
silber/bouncerv1.0.4 (Packagist)
santigarcor/laratrust8.5.6 (Packagist)
Laravel Gate + policiesLaravel 13.x, built in (docs)

Short links used below: SP = spatie/laravel-permission@8.3.2, BO = silber/bouncer@v1.0.4, LT = santigarcor/laratrust@8.5.6.

Feature table

Rowaccess-profiles 0.1.0spatie/laravel-permission 8.3.2silber/bouncer 1.0.4laratrust 8.5.6Plain Gate / policies
Runtime depsPHP 8.3+ + 8 Illuminate packages: auth, cache, console, contracts, database, events, support, view (composer.json)illuminate/* + spatie/laravel-package-tools (composer.json)illuminate/{auth,cache,container,contracts,database} only (composer.json)the whole laravel/framework + kkszymanowski/traitor (composer.json)none
Multi-tenant scopeTwo levels: tenant + scopes inside it, Eloquent models via morphs (ownership, tests)One team_id dimension (teams)One tenant id per request via Bouncer::scope()->to() (README “Multi-tenancy”)One team dimension, passed per call (teams)You write it
One assignment → a set of scopesYes: one assignment + N scope rows, or whole tenant (builder, tests)— (one role row per team)— (abilities can target one model instance, to('edit', $post), not a scope set)— (one role row per team)You write it
DenyYes, deny wins (resolver, tests)—Yes, forbid() (README)—You write it
Expiry / validity windowvalid_from / valid_until; cache TTL capped at the next boundary (cache, window tests, TTL tests)———You write it
Grantor + reason auditAssignment grant/revoke metadata + write events; actor/reason may be null, including system writes with no actor (actor, builder, tests)— (role events exist, opt-in, no grantor/reason: events)— (no events in src/)— (model events roleAdded etc., no grantor/reason: events)You write it
Appointment titletitle column (builder, tests)— (model_has_roles has a composite primary key and no extra columns: migration)——You write it
“Who can do X here?”Access::holders('x', $scope) — deny, validity, scope and conditions applied; request tokens excluded (query, tests)User::permission('x') scope, for the current global team (basic usage)By role only: whereIs('admin') (source); not by abilitywhereHasPermission('x', $team) (source)— (loop over users)
Octane / queue global stateNo global current tenant: target passed per check; memo is a scoped binding (provider, resolver, memo tests)Global team id via setPermissionsTeamId(); must be set per request/job; optional Octane reset listener (cache)Static scope (Models::$scope, source) set by middlewareNone for checks: team is a call argumentNone
Sanctum / API tokensOptional tokenCan intersection by duck typing for model-backed tokens, no Sanctum dependency (resolver, config, tests, deps)— (Passport client-credentials guide only: passport)——tokenCan() by hand
WildcardWrite-time expansion against the catalogue (billing.*) (catalogue, tests)Optional read-time wildcard (wildcard)everything(), toManage(Model) (README)hasPermission('admin.*') (roles and permissions)—
CachePer-request memo + versioned per-tenant cache using standard store operations, no tags (cache, versions, config, tests)One cached permission set, 24 h default, reset on change (cache)Per-request; cross-request optional; full refresh uses cache tags when available (README “Cache”)Optional, on in production by default (config)—
Owner / super-adminNo built-in bypass; access requires a matching assignment (resolver, tests)Recommends Gate::before (super-admin)everything()role checkGate::before
Customer-editable profiles per tenantTemplate + tenant copy (based_on_id) (builder, tests)Team roles (team_id on roles)Scoped rolesRoles are global; teams scope the assignment (team_id only on role_user / permission_user: migration)—
Laravel versions12.69+, 13.30+ (within majors 12 and 13; composer.json)12, 1311, 12, 1310, 11, 12, 13—
MaintenanceNew (changelog)Active: 8.3.0 (2026-07-03), 8.3.1 + 8.3.2 (2026-10-19)Slow: v1.0.2 (2024-03-14), v1.0.3 (2025-02-24), v1.0.4 (2026-03-18)Active: 8.5.4 (2026-02-23), 8.5.5 (2026-03-06), 8.5.6 (2026-10-22)Laravel core

Maintenance dates for the other packages come from the Packagist metadata linked in the version table.

Reading the table honestly

  • spatie/laravel-permission is the safe default for a single-tenant or one-dimension-team app. It has the biggest community. Our package is worth it only when you need the rows where Spatie shows ”—”: scope sets, deny, validity, grantor/title audit.
  • Bouncer is the closest in spirit (it has deny). It lacks validity, audit and a two-level scope, keeps tenant state static, and releases about once a year.
  • Laratrust passes the team explicitly (good for queues) but pulls in the whole framework package and has no deny or expiry.
  • Plain Gate + policies is enough when roles are few and fixed in code. Our package works next to the Gate, not instead of it: host policies call canAccess(); it does not register Gate abilities (provider, policy example test).