immich-app/immich · error · BadRequestException

Cannot remove the last album owner

Error message

Cannot remove the last album owner

What it means

Raised by AlbumService.removeUser when the member being removed is an Owner and is the only owner in the album (albumUsers filtered to role Owner has length 1). This prevents deleting the last owner and leaving the album ownerless; ownership must first be transferred to another member via updateUser before removal.

Solutions

  1. The owner must transfer or delete the album instead of removing themselves
  2. Promote/keep another owner first — though note addUsers cannot create owners, so owner removal is generally impossible by design
  3. Remove non-owner members instead; the owner is removed only with album deletion

Example fix

// before
await api.removeAlbumUser(albumId, ownerId); // 400 if last owner
// after
if (album.ownerId !== userId) {
  await api.removeAlbumUser(albumId, userId);
} else {
  await api.deleteAlbum(albumId); // owner leaves by deleting
}
Defensive patterns

Strategy: validation

Validate before calling

const album = await api.getAlbumInfo(albumId);
const target = album.albumUsers.find(m => m.userId === userId);
if (target?.role === 'Owner') throw new Error('Cannot remove the last/only album owner');

Type guard

function isRemovableMember(userId: string, album: { albumUsers: { user: { id: string }; role: string }[] }): boolean {
  const m = album.albumUsers.find(m => m.user.id === userId);
  return !!m && m.role !== 'Owner';
}

Try / catch

try {
  await api.removeAlbumUser(albumId, userId);
} catch (e) {
  if ((e as Error).message === 'Cannot remove the last album owner') {
    throw new Error('Delete the album or transfer ownership instead');
  }
  throw e;
}

Prevention

When it happens

Trigger: DELETE /albums/:id/user/:userId where the target is the sole Owner of the album (typically the album creator).

Common situations: Owners trying to remove themselves while still the only owner, or admins attempting to clean up members starting with the owner entry.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/749c64292ca52961. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/album.service.ts:331

  }

  async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
    if (userId === 'me') {
      userId = auth.user.id;
    }

    const album = await this.findOrFail(id, auth.user.id, { withAssets: false });

    const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
    if (!exists) {
      throw new BadRequestException('Album not shared with user');
    }

    if (
      exists.role === AlbumUserRole.Owner &&
      album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1
    ) {
      throw new BadRequestException('Cannot remove the last album owner');
    }

    // non-admin can remove themselves
    if (auth.user.id !== userId) {
      await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
    }

    await this.albumUserRepository.delete({ albumId: id, userId });
  }

  async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
    await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });

    const album = await this.findOrFail(id, userId, { withAssets: false });
    const owner = album.albumUsers[0];

    if (owner.user.id === userId) {
      throw new BadRequestException('User is owner');

View on GitHub (pinned to e55ac299a4)