immich-app/immich · warning · BadRequestException
Duplicate items are not allowed: "${key}"
Error message
Duplicate items are not allowed: "${key}" What it means
Thrown by AssetService.upsertBulkMetadata when two items in dto.items produce the same composite key `(assetId, key)`. The key is constructed as the string `(item.assetId, item.key)` and tracked in a Set; a duplicate means the caller sent two metadata values for the same asset+key pair in one batch. BadRequestException (HTTP 400). This is a client-side data error — the server has not written anything yet.
Source
Thrown at server/src/services/asset.service.ts:404
}
const dimensions = getDimensions({
exifImageHeight: asset.exifImageHeight,
exifImageWidth: asset.exifImageWidth,
orientation: asset.orientation,
});
return ocr.map((item) => transformOcrBoundingBox(item, asset.edits, dimensions));
}
async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise<AssetMetadataBulkResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) });
const uniqueKeys = new Set<string>();
for (const item of dto.items) {
const key = `(${item.assetId}, ${item.key})`;
if (uniqueKeys.has(key)) {
throw new BadRequestException(`Duplicate items are not allowed: "${key}"`);
}
uniqueKeys.add(key);
}
return this.assetRepository.upsertBulkMetadata(dto.items);
}
async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise<AssetMetadataResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
const uniqueKeys = new Set<string>();
for (const { key } of dto.items) {
if (uniqueKeys.has(key)) {
throw new BadRequestException(`Duplicate items are not allowed: "${key}"`);
}
uniqueKeys.add(key);View on GitHub (pinned to 199723261c)
Solutions
- Dedupe items by (assetId, key), keeping the desired value, before submitting
- Build the items list from a Map keyed by `${assetId}:${key}` so duplicates overwrite
- Validate the items array client-side and surface duplicates to the user
Example fix
// before
const items = [...fromExif, ...fromXmp]; // may overlap
await sdk.upsertBulkMetadata({ items });
// after
const map = new Map();
for (const it of [...fromExif, ...fromXmp]) map.set(`(${it.assetId}, ${it.key})`, it);
await sdk.upsertBulkMetadata({ items: [...map.values()] }); Defensive patterns
Strategy: validation
Validate before calling
// Dedupe bulk metadata items by (assetId, key) before submitting
const map = new Map<string, AssetMetadataItem>();
for (const it of items) {
map.set(`(${it.assetId}, ${it.key})`, it); // last-wins
}
const deduped = [...map.values()];
if (deduped.length !== items.length) {
console.warn(`Removed ${items.length - deduped.length} duplicate metadata items`);
}
await sdk.upsertBulkMetadata({ items: deduped }); Type guard
function hasNoDuplicateAssetKeys(items: { assetId: string; key: string }[]): boolean {
const seen = new Set<string>();
for (const it of items) {
const k = `(${it.assetId}, ${it.key})`;
if (seen.has(k)) return false;
seen.add(k);
}
return true;
} Prevention
- Build bulk metadata from a Map keyed by `${assetId}:${key}` so duplicates overwrite
- Validate the items array with hasNoDuplicateAssetKeys before submitting
- Merge metadata sources with last-wins semantics rather than array concat
When it happens
Trigger: POST /assets/metadata/bulk-upsert (or the bulk metadata endpoint) where dto.items contains two entries with the same assetId AND the same key. E.g. two items both `{ assetId: 'a1', key: 'exif.fNumber', value: ... }`.
Common situations: Concatenating metadata from multiple sources without deduplication; batch built by merging maps where last-wins was intended; client bug appending instead of replacing.
Related errors
- Asset does not have valid dimensions
- Unsupported file type ${filename}
- May not request original file
- Asset not found or asset is not a video
- Quota has been exceeded!
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/3c0f71b6b36e74e5.
Report an issue: GitHub.