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:
| Table | Columns | Keys |
|---|---|---|
permissions | id, name (unique), description (jsonb, nullable), timestamps | Auto-incrementing id. |
roles | id, name, description (jsonb, nullable), owner_type, owner_id, owner_key, timestamps | Auto-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_role | permission_id, role_id | Both foreign keys cascade on delete; primary key (permission_id, role_id). |
model_roles | role_id, model_type, model_id | role_id cascades on delete; index (model_id, model_type); primary key (role_id, model_id, model_type). |
model_permissions | permission_id, model_type, model_id | permission_id cascades on delete; index (model_id, model_type); primary key (permission_id, model_id, model_type). |
role_assignments | id, role_id, model_type, model_id, scope_type, scope_id, valid_from, valid_until, fingerprint, timestamps | role_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_assignments | id, permission_id, model_type, model_id, scope_type, scope_id, valid_from, valid_until, fingerprint, timestamps | The 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_type | model_id column | Use when your holders… |
|---|---|---|
bigint | unsignedBigInteger | use Laravel’s default auto-incrementing keys (the default) |
uuid | uuid | use HasUuids |
ulid | ulid | use HasUlids |
Set it before you publish and run the migrations — the schema freezes at release:
PERMISSIONS_KEY_TYPE=uuiduse 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 cryptoBy 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.