{"record":{"id":"a46e60f144562a8e","repo":"immich-app/immich","slug":"failed-to-save-shared-link","errorCode":null,"errorMessage":"Failed to save shared link","messagePattern":"Failed to save shared link","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/shared-link.service.ts","lineNumber":115,"sourceCode":"        description: dto.description || null,\n        password: dto.password,\n        expiresAt: dto.expiresAt || null,\n        allowUpload: dto.allowUpload ?? true,\n        allowDownload: dto.showMetadata === false ? false : (dto.allowDownload ?? true),\n        showExif: dto.showMetadata ?? true,\n        slug: dto.slug || null,\n      });\n\n      return mapSharedLink(sharedLink, { stripAssetMetadata: false });\n    } catch (error) {\n      this.handleError(error);\n    }\n  }\n\n  private handleError(error: unknown): never {\n    if ((error as PostgresError).constraint_name === 'shared_link_slug_uq') {\n      this.logger.debug('Shared link with this slug already exists');\n      throw new BadRequestException('Failed to save shared link');\n    }\n    throw error;\n  }\n\n  async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) {\n    await this.findOrFail(auth.user.id, id);\n    try {\n      const sharedLink = await this.sharedLinkRepository.update({\n        id,\n        userId: auth.user.id,\n        description: dto.description,\n        password: dto.password,\n        expiresAt: dto.expiresAt,\n        allowUpload: dto.allowUpload,\n        allowDownload: dto.allowDownload,\n        showExif: dto.showMetadata,\n        slug: dto.slug || null,\n      });","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/shared-link.service.ts#L97-L133","documentation":"SharedLinkService.create() wraps the repository write in try/catch and routes errors through handleError(). If the caught error is a PostgresError whose constraint_name is 'shared_link_slug_uq' (unique constraint on the slug column), it is converted to BadRequestException 'Failed to save shared link' (shared-link.service.ts:115, HTTP 400). The message is generic but the root cause is a duplicate slug.","triggerScenarios":"Creating a shared link with a dto.slug that already exists for another shared link (the slug is globally unique), causing the DB to reject the insert and trip the shared_link_slug_uq constraint.","commonSituations":"User-chosen custom slugs colliding with existing ones, regenerated slugs after a failed attempt, or a front-end defaulting to a common slug like 'photos'.","solutions":["Omit dto.slug to let the server generate a unique random slug.","If supplying a custom slug, make it unique (append a random suffix or timestamp).","On this 400, prompt the user to choose a different slug and retry."],"exampleFix":"// before - colliding slug\nawait create({ type, albumId, slug: 'summer' });\n// after - server-generated unique slug\nawait create({ type, albumId });","handlingStrategy":"try-catch","validationCode":"// If supplying a custom slug, ensure uniqueness; otherwise omit it.\nconst payload = { ...dto };\nif (payload.slug != null && !isSlugAvailable(payload.slug)) {\n  delete payload.slug; // let the server generate one\n}\nawait sharedLinkApi.create(payload);","typeGuard":"const hasCustomSlug = (dto: SharedLinkCreateDto): boolean => typeof dto.slug === 'string' && dto.slug.length > 0;","tryCatchPattern":"try {\n  await sharedLinkApi.create(dto);\n} catch (e) {\n  if (e instanceof BadRequestException && /failed to save shared link/i.test(e.message)) {\n    // likely a slug collision — retry with a server-generated slug\n    const { slug, ...rest } = dto;\n    return sharedLinkApi.create(rest);\n  } else throw e;\n}","preventionTips":["Omit slug to get a unique generated value, or append a random suffix to custom slugs.","On collision, retry without the slug rather than failing the whole action."],"tags":["shared-link","slug","database","unique-constraint"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}