{"record":{"id":"88170c04acb271d6","repo":"immich-app/immich","slug":"crop-action-must-be-the-first-edit-action","errorCode":null,"errorMessage":"Crop action must be the first edit action","messagePattern":"Crop action must be the first edit action","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset.service.ts","lineNumber":571,"sourceCode":"      throw new BadRequestException('Editing GIF images is not supported');\n    }\n\n    if (asset.originalPath?.toLowerCase().endsWith('.svg')) {\n      throw new BadRequestException('Editing SVG images is not supported');\n    }\n\n    // check that crop parameters will not go out of bounds\n    const { width: assetWidth, height: assetHeight } = getDimensions(asset);\n\n    if (!assetWidth || !assetHeight) {\n      throw new BadRequestException('Asset dimensions are not available for editing');\n    }\n\n    const edits = dto.edits as AssetEditActionItem[];\n    const crop = edits.find((e) => e.action === AssetEditAction.Crop);\n    if (crop) {\n      if (edits[0].action !== AssetEditAction.Crop) {\n        throw new BadRequestException('Crop action must be the first edit action');\n      }\n\n      // check that crop parameters will not go out of bounds\n      const { width: assetWidth, height: assetHeight } = getDimensions(asset);\n\n      if (!assetWidth || !assetHeight) {\n        throw new BadRequestException('Asset dimensions are not available for editing');\n      }\n\n      const { x, y, width, height } = crop.parameters;\n      if (x + width > assetWidth || y + height > assetHeight) {\n        throw new BadRequestException('Crop parameters are out of bounds');\n      }\n    }\n\n    const newEdits = await this.assetEditRepository.replaceAll(id, edits);\n    await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } });\n","sourceCodeStart":553,"sourceCodeEnd":589,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/asset.service.ts#L553-L589","documentation":"editAsset enforces ordering: if the edits array contains a Crop action, it must be the first element. Other actions (rotate, flip) change the coordinate space, so a crop that follows them would have ambiguous semantics; Immich rejects any crop that is not edits[0] with this 400.","triggerScenarios":"PUT /api/assets/:id (editAsset) with dto.edits like [{action:'rotate'},{action:'crop',parameters:{...}}] — any edit list where a Crop action appears at index > 0.","commonSituations":"UI pipelines that batch operations in the order the user performed them (rotate then crop); merging accumulated edit history where a crop was appended after an earlier rotation; two clients appending edits to the same asset.","solutions":["Reorder the edits array so the Crop action is element 0 before sending.","Split into two requests: first submit non-crop edits, then submit the crop as the first action of the next replacement.","Alternatively transform the crop coordinates client-side into the rotated space and drop the separate rotate action."],"exampleFix":"// before\nedits = [{ action: 'rotate', parameters: { rotation: 90 } }, { action: 'crop', parameters: { x: 10, y: 10, width: 100, height: 100 } }];\n// after\nedits = [{ action: 'crop', parameters: { x: 10, y: 10, width: 100, height: 100 } }, { action: 'rotate', parameters: { rotation: 90 } }];","handlingStrategy":"validation","validationCode":"const cropIdx = edits.findIndex(e => e.action === 'crop');\nif (cropIdx > 0) throw new Error('Crop must be the first edit action');","typeGuard":null,"tryCatchPattern":"try { await api.editAsset(id, { edits }); } catch (e) { if (e.status === 400) edits = reorderCropFirst(edits) && retry; }","preventionTips":["Always serialize crop before rotate/flip when building edit lists","Centralize edit-list construction in one helper that enforces ordering","Document ordering constraints in your edit-pipeline code"],"tags":["api","validation","ordering","immich"],"backgroundTag":"invalid-argument-value","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"}