{"record":{"id":"3c0f71b6b36e74e5","repo":"immich-app/immich","slug":"duplicate-items-are-not-allowed-key","errorCode":null,"errorMessage":"Duplicate items are not allowed: \"${key}\"","messagePattern":"Duplicate items are not allowed: \"(.+?)\"","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset.service.ts","lineNumber":409,"sourceCode":"    }\n\n    const dimensions = getDimensions({\n      exifImageHeight: asset.exifImageHeight,\n      exifImageWidth: asset.exifImageWidth,\n      orientation: asset.orientation,\n    });\n\n    return ocr.map((item) => transformOcrBoundingBox(item, asset.edits, dimensions));\n  }\n\n  async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise<AssetMetadataBulkResponseDto[]> {\n    await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) });\n\n    const uniqueKeys = new Set<string>();\n    for (const item of dto.items) {\n      const key = `(${item.assetId}, ${item.key})`;\n      if (uniqueKeys.has(key)) {\n        throw new BadRequestException(`Duplicate items are not allowed: \"${key}\"`);\n      }\n\n      uniqueKeys.add(key);\n    }\n\n    return this.assetRepository.upsertBulkMetadata(dto.items);\n  }\n\n  async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise<AssetMetadataResponseDto[]> {\n    await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });\n\n    const uniqueKeys = new Set<string>();\n    for (const { key } of dto.items) {\n      if (uniqueKeys.has(key)) {\n        throw new BadRequestException(`Duplicate items are not allowed: \"${key}\"`);\n      }\n\n      uniqueKeys.add(key);","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/asset.service.ts#L391-L427","documentation":"upsertBulkMetadata rejects a bulk metadata upsert when two items share the same (assetId, key) pair, since the upsert would write the same metadata key twice for one asset. The server builds the composite key string and throws BadRequestException before touching the database. Each asset/key pair must appear at most once in dto.items.","triggerScenarios":"PUT /api/assets/metadata (bulk) with dto.items containing two entries where both assetId and key are identical, e.g. [{assetId:'a',key:'face-1'},{assetId:'a',key:'face-1'}].","commonSituations":"Merging metadata lists from multiple sources (e.g. duplicate face entries in EXIF/ML results); concatenating per-asset arrays without deduplication; re-running a sync job that appends instead of replacing items.","solutions":["Deduplicate dto.items by (assetId, key) before sending the request","If multiple values are intended, use distinct keys (e.g. key-1, key-2) or the dedicated bulk metadata endpoints for multiple values","Log and drop later duplicates client-side when building the payload","Ensure sync jobs replace rather than append metadata items"],"exampleFix":"// before\nconst items = [...oldItems, ...newItems];\nawait api.upsertBulkMetadata({ items });\n// after\nconst seen = new Set<string>();\nconst items = [...oldItems, ...newItems].filter((i) => {\n  const k = `${i.assetId}:${i.key}`;\n  if (seen.has(k)) return false;\n  seen.add(k);\n  return true;\n});\nawait api.upsertBulkMetadata({ items });","handlingStrategy":"validation","validationCode":"const seen = new Set<string>();\nfor (const i of items) {\n  const k = `${i.assetId}:${i.key}`;\n  if (seen.has(k)) throw new Error(`Duplicate metadata item: ${k}`);\n  seen.add(k);\n}","typeGuard":null,"tryCatchPattern":"try { await api.upsertBulkMetadata({ items }); } catch (e) { if (String(e?.response?.data?.message).includes('Duplicate items')) { dedupeItems(); retryOnce(); } else throw e; }","preventionTips":["Always dedupe by (assetId, key) when merging metadata sources","Build items with a Map keyed on the composite key","Avoid append-style sync jobs for metadata","Unit-test payload builders for duplicate keys"],"tags":["metadata","duplicate","validation","rest-api"],"backgroundTag":"schema-validation-failed","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"}