{"record":{"id":"a49d7fdb73c319bb","repo":"BookStackApp/BookStack","slug":"errors-users-cannot-delete-guest","errorCode":null,"errorMessage":"errors.users_cannot_delete_guest","messagePattern":"errors\\.users_cannot_delete_guest","errorType":"exception","errorClass":"NotifyException","httpStatus":null,"severity":"error","filePath":"app/Users/UserRepo.php","lineNumber":257,"sourceCode":"            foreach ($columns as $column) {\n                DB::table($table)\n                    ->where($column, '=', $user->id)\n                    ->update([$column => null]);\n            }\n        }\n    }\n\n    /**\n     * @throws NotifyException\n     */\n    protected function ensureDeletable(User $user): void\n    {\n        if ($this->isOnlyAdmin($user)) {\n            throw new NotifyException(trans('errors.users_cannot_delete_only_admin'), $user->getEditUrl());\n        }\n\n        if ($user->system_name === 'public') {\n            throw new NotifyException(trans('errors.users_cannot_delete_guest'), $user->getEditUrl());\n        }\n    }\n\n    /**\n     * Migrate ownership of items in the system from one user to another.\n     */\n    protected function migrateOwnership(User $fromUser, User|null $toUser): void\n    {\n        $newOwnerValue = $toUser ? $toUser->id : null;\n        DB::table('entities')\n            ->where('owned_by', '=', $fromUser->id)\n            ->update(['owned_by' => $newOwnerValue]);\n    }\n\n    /**\n     * Get an avatar image for a user and set it as their avatar.\n     * Returns early if avatars disabled or not set in config.\n     */","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Users/UserRepo.php#L239-L275","documentation":"ensureDeletable() also throws NotifyException when the target user is the system 'guest' user (system_name === 'public'). The guest user is an internal representation of unauthenticated access and must always exist, so BookStack blocks its deletion with errors.users_cannot_delete_guest and redirects to the user's edit page.","triggerScenarios":"UserRepo::destroy invoked with the user whose system_name property equals 'public' — BookStack's built-in guest/public visibility user.","commonSituations":"Admins browsing the users list attempting to delete the guest entry; scripted cleanups that iterate over all users and hit the guest record; API-driven user management lacking a system-user filter.","solutions":["Exclude users where system_name === 'public' from deletion flows/UI","Filter guest users out of bulk-delete scripts before calling destroy","Treat NotifyException as a redirect-with-message, not a crash — catch it in API wrappers and return a 4xx with the translated message","Restore accidentally modified guest records from backup / reset system_name"],"exampleFix":"// before\nforeach ($users as $u) { $this->userRepo->destroy($u); }\n// after\nforeach ($users as $u) {\n    if ($u->system_name !== 'public') { $this->userRepo->destroy($u); }\n}","handlingStrategy":"validation","validationCode":"// Skip the system guest user\nif ($user->system_name === 'public') {\n    return; // guest user cannot be deleted\n}\n$userRepo->destroy($user);","typeGuard":"function isSystemUser(\\BookStack\\Users\\User $u): bool {\n    return $u->system_name === 'public';\n}","tryCatchPattern":"try {\n    $userRepo->destroy($user);\n} catch (\\BookStack\\Exceptions\\NotifyException $e) {\n    return redirect($user->getEditUrl())->with('error', $e->getMessage());\n}","preventionTips":["Filter users with system_name set (e.g. 'public') from delete UIs and scripts","Query user lists excluding system users: whereNull('system_name')","Never hardcode 'delete all users' maintenance jobs","Handle NotifyException gracefully in API wrappers"],"tags":["php","permissions","users","bookstack"],"backgroundTag":"cannot-delete-system-user","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}