BookStackApp/BookStack · error · PermissionsException

errors.role_system_cannot_be_deleted

Error message

errors.role_system_cannot_be_deleted

What it means

PermissionsRepo::deleteRole refuses to delete a system role (a role whose system_name is in the protected $systemRoles list, e.g. 'admin'). These roles are essential to BookStack's permission model, so deletion throws PermissionsException before any database changes.

Source

Thrown at app/Permissions/PermissionsRepo.php:134

        $role->permissions()->sync($permissions);
    }

    /**
     * Delete a role from the system.
     * Check it's not an admin role or set as default before deleting.
     * If a migration Role ID is specified, the users assigned to the current role
     * will be added to the role of the specified id.
     *
     * @throws PermissionsException
     * @throws Exception
     */
    public function deleteRole(int $roleId, int $migrateRoleId = 0): void
    {
        $role = $this->getRoleById($roleId);

        // Prevent deleting admin role or default registration role.
        if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
            throw new PermissionsException(trans('errors.role_system_cannot_be_deleted'));
        } elseif ($role->id === intval(setting('registration-role'))) {
            throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
        }

        (new DatabaseTransaction(function () use ($migrateRoleId, $role) {
            if ($migrateRoleId !== 0) {
                $newRole = Role::query()->find($migrateRoleId);
                if ($newRole) {
                    $users = $role->users()->pluck('id')->toArray();
                    $newRole->users()->sync($users);
                }
            }

            $role->entityPermissions()->delete();
            $role->jointPermissions()->delete();
            Activity::add(ActivityType::ROLE_DELETE, $role);
            $role->delete();
        }))->run();

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Do not delete the system role; skip roles whose system_name is non-empty/protected in your script
  2. Reassign its users/permissions to another role first if the intent is to retire the role's assignments (though the role itself must remain)
  3. If a custom role wrongly has a system_name, fix the roles table data so only built-in roles carry system names
  4. Wrap deletion in a check: fetch the role and verify it is not a system role before calling deleteRole

Example fix

// before
foreach ($roles as $role) { $repo->deleteRole($role->id); }
// after
foreach ($roles as $role) {
    if ($role->system_name) { continue; }
    $repo->deleteRole($role->id);
}
Defensive patterns

Strategy: validation

Validate before calling

$role = (new PermissionsRepo(app()))->getRoleById($roleId);
if ($role->system_name && in_array($role->system_name, ['admin', 'public'])) {
    throw new \InvalidArgumentException("Role {$roleId} is a system role and cannot be deleted");
}

Type guard

function isDeletableRole(object $role): bool {
    return empty($role->system_name);
}

Try / catch

try {
    $repo->deleteRole($roleId);
} catch (\BookStack\Exceptions\PermissionsException $e) {
    if (str_contains($e->getMessage(), 'role_system_cannot_be_deleted')) {
        // skip or alert: protected system role
    }
}

Prevention

When it happens

Trigger: Calling deleteRole($roleId) (directly or via the roles UI) where the target role has a system_name matching one of the protected system roles, e.g. attempting to delete the Admin role.

Common situations: Cleanup scripts iterating over all roles and deleting them; admin UI misuse; seeders/tests trying to reset roles to a clean state.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/1688a17966453fd4. Report an issue: GitHub.