immich-app/immich · error · BadRequestException

Duplicate items are not allowed

Error message

Duplicate items are not allowed: "${key}"

What it means

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.

Solutions

  1. Deduplicate dto.items by (assetId, key) before sending the request
  2. If multiple values are intended, use distinct keys (e.g. key-1, key-2) or the dedicated bulk metadata endpoints for multiple values
  3. Log and drop later duplicates client-side when building the payload
  4. Ensure sync jobs replace rather than append metadata items

Example fix

// before
const items = [...oldItems, ...newItems];
await api.upsertBulkMetadata({ items });
// after
const seen = new Set<string>();
const items = [...oldItems, ...newItems].filter((i) => {
  const k = `${i.assetId}:${i.key}`;
  if (seen.has(k)) return false;
  seen.add(k);
  return true;
});
await api.upsertBulkMetadata({ items });
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set<string>();
for (const i of items) {
  const k = `${i.assetId}:${i.key}`;
  if (seen.has(k)) throw new Error(`Duplicate metadata item: ${k}`);
  seen.add(k);
}

Try / catch

try { await api.upsertBulkMetadata({ items }); } catch (e) { if (String(e?.response?.data?.message).includes('Duplicate items')) { dedupeItems(); retryOnce(); } else throw e; }

Prevention

When it happens

Trigger: 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'}].

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/3c0f71b6b36e74e5. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/asset.service.ts:409

    }

    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 e55ac299a4)