{"record":{"id":"e610c91d26b1e4a5","repo":"immich-app/immich","slug":"either-query-or-queryassetid-must-be-set","errorCode":null,"errorMessage":"Either `query` or `queryAssetId` must be set","messagePattern":"Either `query` or `queryAssetId` must be set","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/search.service.ts","lineNumber":353,"sourceCode":"          modelName: machineLearning.clip.modelName,\n          language: dto.language,\n        });\n        this.embeddingCache.set(key, embedding);\n      }\n      return embedding;\n    }\n\n    if (dto.queryAssetId) {\n      await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.queryAssetId] });\n      const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId);\n      const assetEmbedding = getEmbeddingResponse?.embedding;\n      if (!assetEmbedding) {\n        throw new BadRequestException(`Asset ${dto.queryAssetId} has no embedding`);\n      }\n      return assetEmbedding;\n    }\n\n    throw new BadRequestException('Either `query` or `queryAssetId` must be set');\n  }\n\n  private async getUserIdsToSearch(auth: AuthDto, visibility?: AssetVisibility): Promise<string[]> {\n    // Locked assets are personal. Never include partner IDs, regardless of A's elevated session.\n    if (visibility === AssetVisibility.Locked) {\n      return [auth.user.id];\n    }\n    const partnerIds = await getMyPartnerIds({\n      userId: auth.user.id,\n      repository: this.partnerRepository,\n      timelineEnabled: true,\n    });\n    return [auth.user.id, ...partnerIds];\n  }\n\n  private mapResponse(\n    assets: MapAsset[],\n    options: AssetMapOptions,","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/search.service.ts#L335-L371","documentation":"resolveEmbedding requires exactly one embedding source: either a text `query` or a `queryAssetId`. When neither is provided it throws this BadRequest, since there is nothing to embed for a smart search.","triggerScenarios":"POST /search/smart (or searchSmart) with a body lacking both `query` and `queryAssetId` — e.g. empty body, or only filter/page/size fields set.","commonSituations":"Clients clearing the search box and submitting anyway; UI code building the DTO conditionally and dropping the query; API consumers confusing smart search with metadata search parameter names.","solutions":["Send a non-empty `query` string for text semantic search","Send `queryAssetId` for similarity-by-asset search","Add client-side validation requiring exactly one of the two before calling the API","Route filter-only requests to metadata search instead of smart search"],"exampleFix":"// before\nawait api.searchSmart({ page: 1 }); // throws\n// after\nconst dto = term ? { query: term } : { queryAssetId: assetId };\nawait api.searchSmart(dto);","handlingStrategy":"validation","validationCode":"if (!dto.query && !dto.queryAssetId) {\n  throw new MissingSearchQuery(); // don't call the API\n}","typeGuard":"function hasSearchSource(dto: { query?: string; queryAssetId?: string }): boolean {\n  return (typeof dto.query === 'string' && dto.query.length > 0) || typeof dto.queryAssetId === 'string';\n}","tryCatchPattern":"try {\n  return await searchService.searchSmart(auth, dto);\n} catch (e) {\n  if (e instanceof BadRequestException && /must be set/.test(e.message)) {\n    return emptyResult();\n  }\n  throw e;\n}","preventionTips":["Validate smart-search DTOs client-side before sending","Never submit an empty search box to smart search","Route filter-only requests to metadata search"],"tags":["search","validation","required-field"],"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"}