BookStackApp/BookStack · error · NotifyException

errors.users_cannot_delete_guest

Error message

errors.users_cannot_delete_guest

What it means

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.

Source

Thrown at app/Users/UserRepo.php:257

            foreach ($columns as $column) {
                DB::table($table)
                    ->where($column, '=', $user->id)
                    ->update([$column => null]);
            }
        }
    }

    /**
     * @throws NotifyException
     */
    protected function ensureDeletable(User $user): void
    {
        if ($this->isOnlyAdmin($user)) {
            throw new NotifyException(trans('errors.users_cannot_delete_only_admin'), $user->getEditUrl());
        }

        if ($user->system_name === 'public') {
            throw new NotifyException(trans('errors.users_cannot_delete_guest'), $user->getEditUrl());
        }
    }

    /**
     * Migrate ownership of items in the system from one user to another.
     */
    protected function migrateOwnership(User $fromUser, User|null $toUser): void
    {
        $newOwnerValue = $toUser ? $toUser->id : null;
        DB::table('entities')
            ->where('owned_by', '=', $fromUser->id)
            ->update(['owned_by' => $newOwnerValue]);
    }

    /**
     * Get an avatar image for a user and set it as their avatar.
     * Returns early if avatars disabled or not set in config.
     */

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Exclude users where system_name === 'public' from deletion flows/UI
  2. Filter guest users out of bulk-delete scripts before calling destroy
  3. Treat NotifyException as a redirect-with-message, not a crash — catch it in API wrappers and return a 4xx with the translated message
  4. Restore accidentally modified guest records from backup / reset system_name

Example fix

// before
foreach ($users as $u) { $this->userRepo->destroy($u); }
// after
foreach ($users as $u) {
    if ($u->system_name !== 'public') { $this->userRepo->destroy($u); }
}
Defensive patterns

Strategy: validation

Validate before calling

// Skip the system guest user
if ($user->system_name === 'public') {
    return; // guest user cannot be deleted
}
$userRepo->destroy($user);

Type guard

function isSystemUser(\BookStack\Users\User $u): bool {
    return $u->system_name === 'public';
}

Try / catch

try {
    $userRepo->destroy($user);
} catch (\BookStack\Exceptions\NotifyException $e) {
    return redirect($user->getEditUrl())->with('error', $e->getMessage());
}

Prevention

When it happens

Trigger: UserRepo::destroy invoked with the user whose system_name property equals 'public' — BookStack's built-in guest/public visibility user.

Common situations: 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.

Related errors


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