Appearance
Roles and permissions
What it does
Decides who can do what, per site. One person can be an editor on one site and a publisher on another. Super admins bypass all of it.
How it works
- Spatie permission, teams mode.
teams => trueandteam_foreign_key => 'site_id'inconfig/permission.php. - Role definitions are global (
site_id = null). Assignments are per site, stored inmodel_has_roleswith the site's id. - The current site is set on every request by the
SetSpatieTeammiddleware, registered as persistent tenant middleware on the tenant panel. Persistent is believed to mean it also runs on Livewire's background requests, so permission checks stay correct after the first page load. - Outside Filament (tinker, seeders, queues) there is no tenant, so call
setPermissionsTeamId($siteId)before checking roles or permissions. - Super admin:
users.is_super_admin(boolean, not mass-assignable) plus aGate::beforethat lets them through every check. Super admins enter every active site without asite_userrow. - Assigning roles: in
/system, the Users resource has a "Site access" repeater that assigns sites and roles.UserSiteAccessdoes the work.
Permissions and built-in roles
Permissions are named area.action.
| Permission | site-admin | publisher | editor |
|---|---|---|---|
page.view, page.create, page.update | yes | yes | yes |
page.delete | yes | no | no |
page.publish | yes | yes | no |
post.view, post.create, post.update | yes | yes | yes |
post.delete | yes | no | no |
post.publish | yes | yes | no |
package.view/create/update/delete/publish | yes | no | no |
media.view, media.create | yes | yes | yes |
media.update | yes | yes | no |
media.delete | yes | no | no |
user.manage | yes | no | no |
site.update | yes | no | no |
audit.view | yes | no | no |
This is the seeder as of October 2026, including the media.* and audit.view additions. site-admin is synced to the whole permissions array, so it picks up any new permission automatically.
Publisher and editor have no package.* permissions at all. If that isn't intended, add them in RolesAndPermissionsSeeder.
Revisions reuse existing permissions: viewing history needs page.view, restoring needs page.update.
After changing the seeder: php artisan db:seed --class=RolesAndPermissionsSeeder, then php artisan permission:cache-reset.
Files
config/permission.php: teams on, customRolemodelapp/Models/Role.php: extends Spatie's role, addssite_assignableand theSYSTEM_ROLESconstantdatabase/seeders/RolesAndPermissionsSeeder.phpapp/Policies/: Page, Package, Post, Site, User, Role (and Media, Activity, PageRevision)app/Services/UserSiteAccess.phpapp/Filament/System/Resources/{Users,Roles}/- Middleware:
SetSpatieTeam
Why it's built this way
- Global roles, per-site assignment gives you one central definition of "editor" while letting each person hold different roles on different sites.
site_assignablecontrols which roles a site admin may hand out, so a client can't give someone a platform role.- Site and Role policies deny everyone except super admins, so clients can't edit platform configuration.
- Policies for read-only resources (the audit trail) hard-code create, update and delete to
false. TheGate::beforebypass would otherwise let a super admin through those checks.