immich-app/immich · error · BadRequestException
Cannot add another owner
Error message
Cannot add another owner
What it means
Raised by AlbumService.addUsers when a requested albumUser entry has role AlbumUserRole.Owner. Albums are single-owner in Immich, so any attempt to add an additional owner (or re-assign ownership via the invite/share endpoint) is rejected with a 400 before the AlbumInvite event is emitted or any album user row is created. The offending input is the role field of an entry in the albumUsers array of AddUsersDto.
Solutions
- Send only 'Editor' or 'Viewer' roles in albumUsers
- Filter out owner-role entries and the existing owner from the payload before calling addUsers
- To transfer ownership, use the dedicated partner/ownership flow if available — there is no add-owner API
Example fix
// before
await api.addAlbumUsers(id, [{ userId, role: 'Owner' }]);
// after
await api.addAlbumUsers(id, [{ userId, role: 'Editor' }]); Defensive patterns
Strategy: validation
Validate before calling
const safe = albumUsers.filter(u => u.role !== 'Owner');
if (safe.length !== albumUsers.length) throw new Error('Owner role is not allowed for album members'); Type guard
function isAssignableRole(r: string): r is 'Editor' | 'Viewer' {
return r === 'Editor' || r === 'Viewer';
} Try / catch
try {
await api.addAlbumUsers(id, albumUsers);
} catch (e) {
if ((e as Error).message === 'Cannot add another owner') {
return api.addAlbumUsers(id, albumUsers.filter(u => u.role !== 'Owner'));
}
throw e;
} Prevention
- Only send Editor/Viewer roles to addUsers
- Send only new members, not the full member list echoed back
- Hide the Owner option in share UIs
- Strip the existing owner entry from payloads built from album info
When it happens
Trigger: PUT /albums/:id/users (or addUsers) with an entry like { userId, role: 'Owner' } in the albumUsers array.
Common situations: Clients echoing back the full member list (including the owner entry) from album info instead of sending only new members, or UIs that expose an owner role option.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/5a2fa845d0119d7d.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/album.service.ts:294
await this.eventRepository.emit('AlbumUpdate', {
id,
userIds: album.albumUsers.map(({ user }) => user.id),
recipientIds: [],
});
}
return results;
}
async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
for (const { userId, role } of albumUsers) {
if (role === AlbumUserRole.Owner) {
throw new BadRequestException('Cannot add another owner');
}
const exists = album.albumUsers.some(({ user: { id } }) => id === userId);
if (exists) {
continue;
}
const user = await this.userRepository.get(userId, {});
if (!user) {
this.logger.debug('Adding user to album failed: user not found');
throw new BadRequestException('Invalid user');
}
await this.albumUserRepository.create({ userId, albumId: id, role });
await this.eventRepository.emit('AlbumInvite', { id, userId, senderName: auth.user.name });
}
return mapAlbum(await this.findOrFail(id, auth.user.id, { withAssets: true }));View on GitHub (pinned to e55ac299a4)