immich-app/immich · error · ForbiddenException
Cannot delete your own account
Error message
Cannot delete your own account
What it means
A ForbiddenException (HTTP 403) thrown by UserAdminService.delete when the authenticated admin's user id equals the target user id being deleted. Immich blocks self-deletion to prevent an admin from locking themselves out or orphaning the only admin account. The check runs after findOrFail but before any soft-delete side effects, so no state mutates.
Source
Thrown at server/src/services/user-admin.service.ts:103
if (dto.pinCode) {
dto.pinCode = await this.cryptoRepository.hashBcrypt(dto.pinCode, SALT_ROUNDS);
}
if (dto.storageLabel === '') {
dto.storageLabel = null;
}
const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date() });
return mapUserAdmin(updatedUser);
}
async delete(auth: AuthDto, id: string, dto: UserAdminDeleteDto): Promise<UserAdminResponseDto> {
const { force } = dto;
await this.findOrFail(id, {});
if (auth.user.id === id) {
throw new ForbiddenException('Cannot delete your own account');
}
await this.albumRepository.softDeleteAll(id);
const status = force ? UserStatus.Removing : UserStatus.Deleted;
const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
await this.eventRepository.emit('UserTrash', user);
if (force) {
await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } });
}
return mapUserAdmin(user);
}
async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
await this.findOrFail(id, { withDeleted: true });View on GitHub (pinned to 199723261c)
Solutions
- Filter the calling user's id out of any bulk delete loop before issuing DELETE /admin/users/:id requests.
- In the admin UI, disable or hide the delete action for the row whose id equals the logged-in admin's id.
- If you genuinely need the account gone, have a different admin delete it, or transfer admin rights to another account first.
- Add a client-side guard: if (auth.user.id === targetId) skip the delete call and surface a message.
Example fix
// before
for (const id of userIds) {
await adminApi.deleteUser(id); // throws 403 when id === me
}
// after
for (const id of userIds) {
if (id === me.id) continue;
await adminApi.deleteUser(id);
} Defensive patterns
Strategy: validation
Validate before calling
function canDeleteUser(authUser, targetId) {
if (authUser.id === targetId) {
return { ok: false, reason: 'Cannot delete your own account' };
}
return { ok: true };
}
// before delete:
const check = canDeleteUser(auth, id);
if (!check.ok) { showError(check.reason); return; } Type guard
const isSelfDelete = (authUserId: string, targetId: string) => authUserId === targetId;
Try / catch
try {
await adminApi.deleteUser(id);
} catch (e) {
if (e.status === 403 && e.message === 'Cannot delete your own account') {
// skip self in bulk loop
continue;
}
throw e;
} Prevention
- Always exclude auth.user.id from bulk delete collections before iterating.
- In admin UIs, render the delete action disabled for the current admin's row.
- Add a pre-flight check in shared admin tooling that fails fast on self-delete.
When it happens
Trigger: Calling DELETE /admin/users/:id where the :id path parameter matches the authenticated user's id (auth.user.id). Common with admin scripts that iterate over all user ids including the caller's own, or a UI that lists the current admin in the delete-eligible list.
Common situations: Bulk admin cleanup scripts that fetch all users and delete each one; frontend admin panels that do not filter the current user out of the delete list; automated test teardown that reuses the admin token for a self-delete call.
Related errors
- Cannot grant permissions you do not have
- Not authenticated with an API Key
- Forbidden
- Missing required permission: ${requestedPermission}
- Shared link access is only allowed in combination with an al
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/0b106722f539a55a.
Report an issue: GitHub.