{"record":{"id":"7c0aa5f328a0e063","repo":"immich-app/immich","slug":"crop-parameters-are-out-of-bounds","errorCode":null,"errorMessage":"Crop parameters are out of bounds","messagePattern":"Crop parameters are out of bounds","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset.service.ts","lineNumber":583,"sourceCode":"    }\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\n    // Return the asset and its applied edits\n    return {\n      assetId: id,\n      edits: newEdits,\n    };\n  }\n\n  async removeAssetEdits(auth: AuthDto, id: string): Promise<void> {\n    await this.requireAccess({ auth, permission: Permission.AssetEditDelete, ids: [id] });\n\n    const asset = await this.assetRepository.getById(id);\n    if (!asset) {","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/asset.service.ts#L565-L601","documentation":"After dimensions are known, editAsset checks the crop rectangle against them: x+width must not exceed assetWidth and y+height must not exceed assetHeight. Violating this means the requested crop extends beyond the actual image, so the 400 is thrown and no edits are saved.","triggerScenarios":"PUT /api/assets/:id (editAsset) with a crop whose parameters satisfy x+width > assetWidth or y+height > assetHeight — e.g. cropping the original-size preview of a downscaled thumbnail, or stale coordinates from before the asset was replaced.","commonSituations":"Client using thumbnail dimensions instead of original dimensions for crop math; coordinates captured against a rotated/preview render; assets replaced with a different resolution while the client kept an old crop UI state.","solutions":["Clamp the crop rectangle to asset dimensions before sending: width = Math.min(width, assetWidth - x), same for height.","Use the asset's original width/height (exifInfo.exifImageWidth/Height), not the preview/thumbnail size, to compute coordinates.","Refresh the asset from the server before rendering the crop UI to avoid stale coordinates.","Scale stored crop coordinates if the source image resolution changed."],"exampleFix":"// before\nawait api.editAsset(assetId, { edits: [{ action: 'crop', parameters: { x, y, width, height } }] });\n// after\nconst w = Math.min(width, asset.exifInfo.exifImageWidth - x);\nconst h = Math.min(height, asset.exifInfo.exifImageHeight - y);\nawait api.editAsset(assetId, { edits: [{ action: 'crop', parameters: { x, y, width: w, height: h } }] });","handlingStrategy":"validation","validationCode":"const { exifImageWidth: aw, exifImageHeight: ah } = asset.exifInfo;\nif (x < 0 || y < 0 || x + width > aw || y + height > ah) {\n  throw new Error('Crop rectangle exceeds asset bounds');\n}","typeGuard":null,"tryCatchPattern":"try { await api.editAsset(id, { edits }); } catch (e) { if (e.status === 400 && /out of bounds/.test(e.message)) clampAndRetry(crop, asset); }","preventionTips":["Compute crop coordinates against original dimensions, not thumbnails/previews","Clamp rectangles client-side before submit","Refresh asset state before rendering crop UI to avoid stale coordinates"],"tags":["api","validation","crop","geometry","immich"],"backgroundTag":"value-out-of-range","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"}