{"record":{"id":"76602efdc529a7fb","repo":"immich-app/immich","slug":"invalid-shared-link-type","errorCode":null,"errorMessage":"Invalid shared link type","messagePattern":"Invalid shared link type","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/shared-link.service.ts","lineNumber":153,"sourceCode":"    } catch (error) {\n      this.handleError(error);\n    }\n  }\n\n  async remove(auth: AuthDto, id: string): Promise<void> {\n    const sharedLink = await this.findOrFail(auth.user.id, id);\n    await this.sharedLinkRepository.remove(sharedLink.id);\n  }\n\n  // TODO: replace `userId` with permissions and access control checks\n  private findOrFail(userId: string, id: string) {\n    return findOrFail(() => this.sharedLinkRepository.get(userId, id), 'Shared link');\n  }\n\n  async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {\n    const sharedLink = await this.findOrFail(auth.user.id, id);\n    if (sharedLink.type !== SharedLinkType.Individual) {\n      throw new BadRequestException('Invalid shared link type');\n    }\n\n    const existingAssetIds = new Set(sharedLink.assets.map((asset) => asset.id));\n    const notPresentAssetIds = dto.assetIds.filter((assetId) => !existingAssetIds.has(assetId));\n    const allowedAssetIds = await this.checkAccess({\n      auth,\n      permission: Permission.AssetShare,\n      ids: notPresentAssetIds,\n    });\n\n    const results: AssetIdsResponseDto[] = [];\n    for (const assetId of dto.assetIds) {\n      const hasAsset = existingAssetIds.has(assetId);\n      if (hasAsset) {\n        results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE });\n        continue;\n      }\n","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/shared-link.service.ts#L135-L171","documentation":"SharedLinkService.addAssets() only operates on SharedLinkType.Individual links; if the resolved link's type is not Individual it throws BadRequestException 'Invalid shared link type' (shared-link.service.ts:153, HTTP 400). Album-type links derive their assets from the album, so per-asset add is not permitted.","triggerScenarios":"POST /shared-links/:id/assets (addAssets) targeting a SharedLinkType.Album link. Only individual links accept an explicit asset list.","commonSituations":"Client reusing the add-assets flow for an album share, or stale state where a link's type changed from Individual to Album.","solutions":["Only call addAssets on links whose type is SharedLinkType.Individual.","To change an album shared link's contents, add/remove assets from the underlying album instead.","Check link.type before exposing the add-assets action in the UI."],"exampleFix":"// before\nawait addAssets(albumLink.id, { assetIds });\n// after\nif (link.type === SharedLinkType.Individual) {\n  await addAssets(link.id, { assetIds });\n}","handlingStrategy":"type-guard","validationCode":"if (link.type !== SharedLinkType.Individual) {\n  throw new Error('addAssets only applies to individual shared links.');\n}\nawait sharedLinkApi.addAssets(link.id, { assetIds });","typeGuard":"const isIndividualLink = (link: { type: SharedLinkType }): link is { type: SharedLinkType.Individual } & typeof link =>\n  link.type === SharedLinkType.Individual;","tryCatchPattern":"try {\n  await sharedLinkApi.addAssets(id, { assetIds });\n} catch (e) {\n  if (e instanceof BadRequestException && /shared link type/i.test(e.message)) {\n    // route the user to manage the album's assets instead\n    navigateToAlbum(link.albumId);\n  } else throw e;\n}","preventionTips":["Check link.type before exposing the add-assets action.","For album links, modify the album's asset membership directly."],"tags":["shared-link","asset","validation"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}