{"record":{"id":"af69042f5bcaed61","repo":"immich-app/immich","slug":"invalid-assetids","errorCode":null,"errorMessage":"Invalid assetIds","messagePattern":"Invalid assetIds","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/shared-link.service.ts","lineNumber":81,"sourceCode":"\n  async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {\n    const sharedLink = await this.findOrFail(auth.user.id, id);\n    return mapSharedLink(sharedLink, { stripAssetMetadata: false });\n  }\n\n  async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {\n    switch (dto.type) {\n      case SharedLinkType.Album: {\n        if (!dto.albumId) {\n          throw new BadRequestException('Invalid albumId');\n        }\n        await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [dto.albumId] });\n        break;\n      }\n\n      case SharedLinkType.Individual: {\n        if (!dto.assetIds || dto.assetIds.length === 0) {\n          throw new BadRequestException('Invalid assetIds');\n        }\n\n        await this.requireAccess({ auth, permission: Permission.AssetShare, ids: dto.assetIds });\n\n        break;\n      }\n    }\n\n    try {\n      const sharedLink = await this.sharedLinkRepository.create({\n        key: this.cryptoRepository.randomBytes(50),\n        userId: auth.user.id,\n        type: dto.type,\n        albumId: dto.albumId || null,\n        assetIds: dto.assetIds,\n        description: dto.description || null,\n        password: dto.password,\n        expiresAt: dto.expiresAt || null,","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/shared-link.service.ts#L63-L99","documentation":"For SharedLinkType.Individual, SharedLink.create requires a non-empty assetIds array; without it the link would share nothing. An empty or missing array triggers BadRequestException('Invalid assetIds') before the per-asset share permission check.","triggerScenarios":"POST /shared-links with dto.type = SharedLinkType.Individual and dto.assetIds undefined, null, or an empty array.","commonSituations":"User deselects all assets in the share dialog but proceeds; batch selection lost on page navigation; asset list filtered to zero items before building the payload.","solutions":["Provide at least one asset id in assetIds for Individual links.","Disable/validate the submit action when the asset selection is empty.","Use type Album with an albumId if the intent is sharing a whole album."],"exampleFix":"// before\nawait api.createSharedLink({ type: SharedLinkType.Individual, assetIds: [] });\n// after\nawait api.createSharedLink({ type: SharedLinkType.Individual, assetIds: selectedAssetIds });","handlingStrategy":"validation","validationCode":"if (dto.type === SharedLinkType.Individual && (!dto.assetIds || dto.assetIds.length === 0)) {\n  throw new Error('Individual shared links require at least one assetId');\n}","typeGuard":"const hasAssets = (dto) =>\n  Array.isArray(dto.assetIds) && dto.assetIds.length > 0 && dto.assetIds.every((id) => typeof id === 'string');","tryCatchPattern":"try {\n  await api.createSharedLink(dto);\n} catch (e) {\n  if (e.status === 400 && e.message === 'Invalid assetIds') {\n    // prompt: select at least one asset\n  } else {\n    throw e;\n  }\n}","preventionTips":["Disable the create button while the asset selection is empty.","Preserve selection across UI navigation.","Assert non-empty assetIds in client-side form validation."],"tags":["shared-link","validation","missing-field","assets"],"backgroundTag":"missing-required-argument","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}