immich-app/immich · warning · BadRequestException
Either `query` or `queryAssetId` must be set
Error message
Either `query` or `queryAssetId` must be set
What it means
Thrown by SearchService.searchSmart when neither dto.query nor dto.queryAssetId is provided. The smart-search endpoint needs a source for the embedding (free text or an anchor asset); with neither, no embedding can be produced. BadRequestException -> HTTP 400.
Source
Thrown at server/src/services/search.service.ts:174
const key = machineLearning.clip.modelName + dto.query + dto.language;
embedding = this.embeddingCache.get(key);
if (!embedding) {
embedding = await this.machineLearningRepository.encodeText(dto.query, {
modelName: machineLearning.clip.modelName,
language: dto.language,
});
this.embeddingCache.set(key, embedding);
}
} else if (dto.queryAssetId) {
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.queryAssetId] });
const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId);
const assetEmbedding = getEmbeddingResponse?.embedding;
if (!assetEmbedding) {
throw new BadRequestException(`Asset ${dto.queryAssetId} has no embedding`);
}
embedding = assetEmbedding;
} else {
throw new BadRequestException('Either `query` or `queryAssetId` must be set');
}
const page = dto.page ?? 1;
const size = dto.size || 100;
const { hasNextPage, items } = await this.searchRepository.searchSmart(
{ page, size },
{
...dto,
userIds: await userIds,
embedding,
visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
},
);
return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
}
async getAssetsByCity(auth: AuthDto): Promise<AssetResponseDto[]> {
const userIds = await this.getUserIdsToSearch(auth);View on GitHub (pinned to 199723261c)
Solutions
- Require at least one of query or queryAssetId in the DTO validation before calling the service.
- Disable the submit button client-side until one field is non-empty.
- Use class-validator @ValidateIf or a custom DTO decorator to enforce one-of at the boundary.
Example fix
// before
} else {
throw new BadRequestException('Either `query` or `queryAssetId` must be set');
}
// DTO-level validation (preferred)
@ValidateIf((o) => !o.queryAssetId)
@IsNotEmpty()
@IsString()
query?: string;
@ValidateIf((o) => !o.query)
@IsNotEmpty()
@IsString()
queryAssetId?: string; Defensive patterns
Strategy: validation
Validate before calling
// Require one of query / queryAssetId before calling the service.
if (!dto.query && !dto.queryAssetId) {
throw new Error('Smart search requires a query or queryAssetId');
}
await searchService.searchSmart(auth, dto); Type guard
const hasSmartSearchInput = (d: SmartSearchDto): boolean => (!!d.query && d.query.trim().length > 0) || !!d.queryAssetId;
Prevention
- Enforce one-of {query, queryAssetId} at the DTO level with class-validator.
- Disable the submit button until one field is populated.
- Validate before the network call; this is a client-side bug, not a server one.
When it happens
Trigger: POST /search/smart with an empty body or only filter fields (page, size, visibility) but no `query` and no `queryAssetId`.
Common situations: Client submits the search form before the user typed anything and with no asset selected; DTO validation relaxed at the boundary; a 'search' button wired to fire on focus loss.
Related errors
- Shared link access is only allowed in combination with an al
- Smart search is not enabled
- Unsupported file type ${filename}
- May not request original file
- Asset not found or asset is not a video
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/e610c91d26b1e4a5.
Report an issue: GitHub.