immich-app/immich · warning · BadRequestException
Shared link access is only allowed in combination with an…
Error message
Shared link access is only allowed in combination with an albumIds filter
What it means
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.
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
Example fix
// before
const res = await api.searchMetadata({}); // throws with shared link
// after
const res = await api.searchMetadata({ albumIds: [sharedAlbumId] }); Defensive patterns
Strategy: validation
Validate before calling
if (isSharedLinkSession() && !(dto.albumIds?.length)) {
// don't call the API; show album-only UI
return emptyResult();
} Type guard
function isAlbumScopedSearch(dto: { albumIds?: string[] }): boolean {
return Array.isArray(dto.albumIds) && dto.albumIds.length > 0;
} Try / catch
try {
return await searchService.searchMetadata(auth, dto);
} catch (e) {
if (e instanceof BadRequestException && /albumIds filter/.test(e.message)) {
return emptyResult();
}
throw e;
} Prevention
- Always attach albumIds for shared-link sessions
- Hide global search for shared-link visitors
- Centralize shared-link request building in one client helper
When it happens
Trigger: GET /search/metadata with a shared-link key (x-immich-shared-link header / key param) but without albumIds, or with an empty albumIds array.
Common situations: 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.
Understand the failure class
Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.
Related errors
- Asset has no embedding
- Denied access to admin only route
- Denied access to non-shared route
- Either `query` or `queryAssetId` must be set
- Elevated permission is required
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/c7fb0cac50990523.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/search.service.ts:94
return this.searchMetadataV3(auth, dto);
}
if (dto.visibility === AssetVisibility.Locked) {
requireElevatedPermission(auth);
}
let checksum: Buffer | undefined;
if (dto.checksum) {
const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
checksum = Buffer.from(dto.checksum, encoding);
}
let userIds: string[] | undefined;
if (dto.albumIds && dto.albumIds.length > 0) {
await this.requireAccess({ auth, ids: dto.albumIds, permission: Permission.AlbumRead });
} else if (auth.sharedLink) {
throw new BadRequestException('Shared link access is only allowed in combination with an albumIds filter');
} else {
userIds = await this.getUserIdsToSearch(auth, dto.visibility);
}
const page = dto.page ?? 1;
const size = dto.size;
const { hasNextPage, items } = await this.searchRepository.searchMetadata(
{ page, size },
{
...dto,
checksum,
visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
userIds,
viewingUserId: auth.user.id,
orderDirection: dto.order ?? AssetOrder.Desc,
},
);
View on GitHub (pinned to e55ac299a4)