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

Database schema & key types

Seven tables back the package — the five 2.0 tables and the two assignment tables 2.1 adds, which also gives roles an owner. Table names come from table_names; column names are fixed:

TableColumnsKeys
permissionsid, name (unique), description (jsonb, nullable), timestampsAuto-incrementing id.
rolesid, name, description (jsonb, nullable), owner_type, owner_id, owner_key, timestampsAuto-incrementing id. Unique (owner_key, name) — owner_key is '' for a global role, so global names stay unique; indexes (owner_type, owner_id) and name. Before the 2.1 migration: unique name, no owner columns.
permission_rolepermission_id, role_idBoth foreign keys cascade on delete; primary key (permission_id, role_id).
model_rolesrole_id, model_type, model_idrole_id cascades on delete; index (model_id, model_type); primary key (role_id, model_id, model_type).
model_permissionspermission_id, model_type, model_idpermission_id cascades on delete; index (model_id, model_type); primary key (permission_id, model_id, model_type).
role_assignmentsid, role_id, model_type, model_id, scope_type, scope_id, valid_from, valid_until, fingerprint, timestampsrole_id cascades on delete; scope and window nullable; fingerprint (sha256 of holder, role, scope and window) unique; indexes (model_id, model_type, scope_type, scope_id), (scope_type, scope_id) and valid_until.
permission_assignmentsid, permission_id, model_type, model_id, scope_type, scope_id, valid_from, valid_until, fingerprint, timestampsThe same, for direct permissions; permission_id cascades on delete.

Holder links are polymorphic (model_type + model_id), so any Eloquent model can hold roles and permissions — not just users. Custom morph-map aliases are stored in the pivots as they are everywhere else in Laravel.

Holder key type

key_type describes the models that hold roles and permissions — never this package’s own tables, which always keep an auto-incrementing key. It types the model_id column on the two polymorphic pivots:

key_typemodel_id columnUse when your holders…
bigintunsignedBigIntegeruse Laravel’s default auto-incrementing keys (the default)
uuiduuiduse HasUuids
uliduliduse HasUlids

Set it before you publish and run the migrations — the schema freezes at release:

PERMISSIONS_KEY_TYPE=uuid
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Foundation\Auth\User as Authenticatable;
use RoundlyConsulting\Permissions\Concerns\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
    use HasUuids; // pairs with PERMISSIONS_KEY_TYPE=uuid
}

Unset or blank reads as bigint; an unrecognized value throws the toolkit’s InvalidConfigurationException rather than silently building bigint keys.

Scope key type

assignments.scope_key_type does the same for scope and role-owner models: it types scope_id on both assignment tables and roles.owner_id. Not set, it reads as key_type. One scope_id column holds every scope model’s key, so all your scopes share one key type. Window columns are dateTime, not timestamp — stored in UTC to the second, with no MySQL session time-zone shift and no 2038 limit.

Hard deletes by design

Roles and permissions do not use soft deletes: the unique name index would clash with soft-deleted rows, and every pivot cascades on delete — removing a role or permission removes its grants with it.

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.