NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Permissions for Laravel

The Permissions facade

Everything goes through the Permissions facade (auto-aliased as Permissions). Its root is RoundlyConsulting\Permissions\PermissionsManager, a singleton you can also inject. Every name-taking method accepts a string or a BackedEnum:

use RoundlyConsulting\Permissions\Facades\Permissions;

// Catalog
Permissions::role('editor');                       // find or create — Role
Permissions::permission('posts.edit');             // find or create — Permission
Permissions::findRole('editor');                   // ?Role
Permissions::findPermission('posts.edit');         // ?Permission — the full row
Permissions::roles();                              // Collection<Role>, ordered by name
Permissions::permissions();                        // the cached catalog: id + name
Permissions::exists('posts.edit');                 // bool, from the cache
Permissions::syncFrom(PostPermission::class);      // SyncResult
Permissions::syncRolesFrom(RoleName::class);       // SyncResult

// Grants for one holder — every verb returns the holder
Permissions::for($user)->assignRole('editor');
Permissions::for($user)->removeRole('editor');
Permissions::for($user)->syncRoles(['editor']);
Permissions::for($user)->givePermissionTo('posts.edit');
Permissions::for($user)->revokePermissionTo('posts.edit');
Permissions::for($user)->syncPermissions([PostPermission::Edit]);
Permissions::for($user)->forgetAllAuthorization();

// Scopes, windows and owned roles — need permissions.assignments.enabled
Permissions::for($user)->in($project)->until($friday)->assignRole('editor');
Permissions::for($user)->in($project)->hasPermissionTo('posts.edit'); // bool
Permissions::scope($project);                      // PermissionScope — the Gate argument
Permissions::owner($tenant)->role('reviewer');     // OwnedRoles — a tenant's own roles
Permissions::resolveScopeUsing($resolver);         // ambient scope for scope-less Gate checks
Permissions::resolveRoleOwnerUsing($resolver);     // let a scope use another owner's roles
Permissions::forgetScope($project);                // int — rows and owned roles deleted
Permissions::pruneExpired();                       // int — rows whose window has ended

// Housekeeping and configuration
Permissions::cache()->forget();
Permissions::cache()->flushMemo();
Permissions::pruneOrphans();                       // int — rows deleted
Permissions::roleModel();                          // class-string<Role>
Permissions::permissionModel();                    // class-string<Permission>

// Tests
$fake = Permissions::fake();                       // PermissionsFake

Methods

MethodReturnsPurpose
role($name)RoleFind or create a role, as your configured model.
permission($name)PermissionFind or create a permission, as your configured model.
findRole($name)?RoleLook a role up by name; null when missing. A live query.
findPermission($name)?PermissionLook a permission up by name — the full row, description included.
roles()Collection<Role>Every role, ordered by name. A live query.
permissions()Collection<Permission>The cached permission catalog — id and name only.
exists($name)boolWhether a permission is registered, answered from the cache.
syncFrom(Enum::class)SyncResultRegister one permission per case of a backed enum. Additive.
syncRolesFrom(Enum::class)SyncResultRegister one role per case of a backed enum. Additive.
for($holder)HolderGrantsRole and permission writes and reads for one holder; in(), from(), until(), between() and at() scope and time-box it.
owner($owner)OwnedRolesThe roles one owner — a tenant, a team — defines for itself.
scope($scopes)PermissionScopeThe Gate argument for a scoped check; several scopes mean any of them.
resolveScopeUsing(?Closure)voidGive scope-less Gate checks an ambient scope; null removes it.
resolveRoleOwnerUsing(?Closure)voidLet a scope also use another owner’s roles; null removes it.
forgetScope($scope)intDelete every assignment in a scope and every role it owns.
pruneExpired(?$endedBefore)intDelete assignment rows whose window ended by then (default now).
cache()PermissionCacheforget() drops the cached catalog; flushMemo() drops only this process’s memo.
pruneOrphans()intDelete grant rows whose holder no longer exists; returns the count.
roleModel() / permissionModel()class-stringYour configured model classes.
fake()PermissionsFakeSwap in the recording fake — facade only.

Writes for one holder: for()

Permissions::for($holder) returns a HolderGrants handle with the same write verbs as the traits. Every verb returns the holder:

use RoundlyConsulting\Permissions\Facades\Permissions;

Permissions::for($user)->assignRole('editor');
Permissions::for($user)->syncPermissions([PostPermission::Edit]);

$role = Permissions::role('editor');
Permissions::for($role)->givePermissionTo('posts.edit'); // a Role holds permissions

// The trait methods are sugar over the same handle:
$user->assignRole('editor'); // === Permissions::for($user)->assignRole('editor')
Permissions::for($holder)->…DoesHolder must
assignRole(...$roles)Additively assign roles.use HasRoles
removeRole(...$roles)Detach roles.use HasRoles
syncRoles($roles)Make the given roles the exact set.use HasRoles
givePermissionTo(...$permissions)Additively grant direct permissions.use HasPermissions — a Role or any HasRoles model
revokePermissionTo(...$permissions)Detach direct permissions.use HasPermissions
syncPermissions($permissions)Make the given direct permissions the exact set.use HasPermissions
forgetAllAuthorization()Detach every role and direct permission — with assignments on, also every scoped and time-boxed row in every scope.use HasRoles
in($scopes)Scope the handle: a model, a list (checks: any of them; writes: exactly one) or null for unscoped. Returns a new handle.use HasRoles; assignments on
from() / until() / between()The window for writes — start inclusive, end exclusive, stored in UTC to the second. Returns a new handle.use HasRoles; assignments on
at($moment)Answer reads as of that moment; writes ignore it.use HasRoles; assignments on
hasRole() / hasAnyRole() / hasPermissionTo()Check in the handle’s scopes at its check time — directly or through a role.use HasRoles
roleNames() / permissionNames() / assignments()What the holder has: names, or every grant as an Assignment.use HasRoles

The scope is a boundary. Role writes refuse a model without HasRoles — including a Role, which holds permissions, never roles. Permission writes refuse a model without HasPermissions, and both refuse a holder that has no key yet. Each refusal is a PermissionException, thrown before anything is written; unknown names throw RoleDoesNotExist or PermissionDoesNotExist.

Where each part is documented

  • role(), permission(), findRole(), findPermission(), roles() — Roles & permissions.
  • syncFrom(), syncRolesFrom() — Backed enums.
  • for()->… role verbs — Assigning roles; permission verbs — Granting permissions.
  • permissions(), exists(), cache() — Caching.
  • pruneOrphans() — Deleting holders.
  • for()->in(), from(), until(), between(), at(), scope(), resolveScopeUsing(), forgetScope(), pruneExpired() — Scoped & time-boxed grants.
  • owner(), resolveRoleOwnerUsing() — Tenant-defined roles.
  • fake() — Testing.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.