{"record":{"id":"c7fb0cac50990523","repo":"immich-app/immich","slug":"shared-link-access-is-only-allowed-in-combination","errorCode":null,"errorMessage":"Shared link access is only allowed in combination with an albumIds filter","messagePattern":"Shared link access is only allowed in combination with an albumIds filter","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/search.service.ts","lineNumber":94,"sourceCode":"      return this.searchMetadataV3(auth, dto);\n    }\n\n    if (dto.visibility === AssetVisibility.Locked) {\n      requireElevatedPermission(auth);\n    }\n\n    let checksum: Buffer | undefined;\n    if (dto.checksum) {\n      const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';\n      checksum = Buffer.from(dto.checksum, encoding);\n    }\n\n    let userIds: string[] | undefined;\n\n    if (dto.albumIds && dto.albumIds.length > 0) {\n      await this.requireAccess({ auth, ids: dto.albumIds, permission: Permission.AlbumRead });\n    } else if (auth.sharedLink) {\n      throw new BadRequestException('Shared link access is only allowed in combination with an albumIds filter');\n    } else {\n      userIds = await this.getUserIdsToSearch(auth, dto.visibility);\n    }\n\n    const page = dto.page ?? 1;\n    const size = dto.size;\n    const { hasNextPage, items } = await this.searchRepository.searchMetadata(\n      { page, size },\n      {\n        ...dto,\n        checksum,\n        visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),\n        userIds,\n        viewingUserId: auth.user.id,\n        orderDirection: dto.order ?? AssetOrder.Desc,\n      },\n    );\n","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/search.service.ts#L76-L112","documentation":"In searchMetadata, a request authenticated via a shared link must include a non-empty albumIds filter; otherwise the visitor would have access to assets outside the shared context. Since shared-link users have no own 'universe' of assets, the endpoint refuses the unscoped search with this BadRequest.","triggerScenarios":"GET /search/metadata with a shared-link key (x-immich-shared-link header / key param) but without albumIds, or with an empty albumIds array.","commonSituations":"Shared-link visitors hitting search APIs directly; frontend components calling metadata search without passing the album context; API integrations reusing code paths meant for logged-in users with a shared-link session.","solutions":["Add albumIds (of the shared album) to every search request made with a shared link","Authenticate with a normal user session instead of a shared link when unscoped search is needed","Check the client that albumIds are actually being forwarded for shared-link sessions","Return an empty/filtered result in your UI when albumIds is absent for shared-link visitors"],"exampleFix":"// before\nconst res = await api.searchMetadata({}); // throws with shared link\n// after\nconst res = await api.searchMetadata({ albumIds: [sharedAlbumId] });","handlingStrategy":"validation","validationCode":"if (isSharedLinkSession() && !(dto.albumIds?.length)) {\n  // don't call the API; show album-only UI\n  return emptyResult();\n}","typeGuard":"function isAlbumScopedSearch(dto: { albumIds?: string[] }): boolean {\n  return Array.isArray(dto.albumIds) && dto.albumIds.length > 0;\n}","tryCatchPattern":"try {\n  return await searchService.searchMetadata(auth, dto);\n} catch (e) {\n  if (e instanceof BadRequestException && /albumIds filter/.test(e.message)) {\n    return emptyResult();\n  }\n  throw e;\n}","preventionTips":["Always attach albumIds for shared-link sessions","Hide global search for shared-link visitors","Centralize shared-link request building in one client helper"],"tags":["search","shared-link","authorization"],"backgroundTag":"invalid-query-parameter","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"}