immich-app/immich · error · BadRequestException
Failed to save shared link
Error message
Failed to save shared link
What it means
handleError wraps shared-link writes and converts a Postgres unique-constraint violation on shared_link_slug_uq (duplicate slug/key) into BadRequestException('Failed to save shared link'); any other error is rethrown untouched. It is invoked by create and update.
Solutions
- Choose a different slug/key value for the link and retry.
- On create, retry — the service will generate a fresh slug.
- If collisions recur programmatically, generate slugs with a random suffix.
- Inspect server debug logs ('Shared link with this slug already exists') to confirm the cause.
Example fix
// before
await api.updateSharedLink(id, { slug: existingSlug });
// after
await api.updateSharedLink(id, { slug: existingSlug + '-2' }); Defensive patterns
Strategy: retry
Try / catch
try {
return await api.createSharedLink(dto);
} catch (e) {
if (e.status === 400 && e.message === 'Failed to save shared link') {
// duplicate slug: retry with a modified slug
return await api.createSharedLink({ ...dto, slug: `${dto.slug}-${Date.now()}` });
}
throw e;
} Prevention
- Add a random/timestamp suffix to custom slugs.
- Don't reuse slugs of deleted-but-soft-retained links.
- Handle the 400 distinctly from other failures — the message is intentionally generic.
- Check server debug logs to confirm the slug constraint caused the failure.
When it happens
Trigger: Creating or updating a shared link whose generated/edited slug collides with an existing row, violating the shared_link_slug_uq unique constraint in Postgres.
Common situations: Manually editing a link's slug/slug-like key to one already in use; a slug-collision in the generated short key; retries inserting the same link twice.
Related errors
- Admin account does not exist
- Album must have an owner
- Column 'embedding' does not exist in table
- Could not find asset
- Could not retrieve dimension size of column
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/a46e60f144562a8e.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/shared-link.service.ts:115
description: dto.description || null,
password: dto.password,
expiresAt: dto.expiresAt || null,
allowUpload: dto.allowUpload ?? true,
allowDownload: dto.showMetadata === false ? false : (dto.allowDownload ?? true),
showExif: dto.showMetadata ?? true,
slug: dto.slug || null,
});
return mapSharedLink(sharedLink, { stripAssetMetadata: false });
} catch (error) {
this.handleError(error);
}
}
private handleError(error: unknown): never {
if ((error as PostgresError).constraint_name === 'shared_link_slug_uq') {
this.logger.debug('Shared link with this slug already exists');
throw new BadRequestException('Failed to save shared link');
}
throw error;
}
async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) {
await this.findOrFail(auth.user.id, id);
try {
const sharedLink = await this.sharedLinkRepository.update({
id,
userId: auth.user.id,
description: dto.description,
password: dto.password,
expiresAt: dto.expiresAt,
allowUpload: dto.allowUpload,
allowDownload: dto.allowDownload,
showExif: dto.showMetadata,
slug: dto.slug || null,
});View on GitHub (pinned to e55ac299a4)