immich-app/immich · warning · BadRequestException

Crop parameters are out of bounds

Error message

Crop parameters are out of bounds

What it means

Thrown by editAsset inside the crop block when the requested crop rectangle exceeds the asset bounds: crop.parameters.x + width > assetWidth OR y + height > assetHeight. The server validates geometry against the asset's real dimensions and rejects oversized/off-edge rectangles with 400 BadRequest.

Source

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

    }

    const edits = dto.edits as AssetEditActionItem[];
    const crop = edits.find((e) => e.action === AssetEditAction.Crop);
    if (crop) {
      if (edits[0].action !== AssetEditAction.Crop) {
        throw new BadRequestException('Crop action must be the first edit action');
      }

      // check that crop parameters will not go out of bounds
      const { width: assetWidth, height: assetHeight } = getDimensions(asset);

      if (!assetWidth || !assetHeight) {
        throw new BadRequestException('Asset dimensions are not available for editing');
      }

      const { x, y, width, height } = crop.parameters;
      if (x + width > assetWidth || y + height > assetHeight) {
        throw new BadRequestException('Crop parameters are out of bounds');
      }
    }

    const newEdits = await this.assetEditRepository.replaceAll(id, edits);
    await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } });

    // Return the asset and its applied edits
    return {
      assetId: id,
      edits: newEdits,
    };
  }

  async removeAssetEdits(auth: AuthDto, id: string): Promise<void> {
    await this.requireAccess({ auth, permission: Permission.AssetEditDelete, ids: [id] });

    const asset = await this.assetRepository.getById(id);
    if (!asset) {

View on GitHub (pinned to 199723261c)

Solutions

  1. Clamp the crop rectangle to [0,0,assetWidth,assetHeight] before submitting.
  2. Compute crop coordinates from the same width/height the server reports (asset.exifInfo width/height).
  3. Subtract a 1px safety margin to avoid off-by-one overshoot.

Example fix

// before
const crop = { x, y, width, height };

// after
const x = Math.max(0, Math.min(crop.x, assetWidth - 1));
const y = Math.max(0, Math.min(crop.y, assetHeight - 1));
const width = Math.min(crop.width, assetWidth - x);
const height = Math.min(crop.height, assetHeight - y);
const crop = { x, y, width, height };
Defensive patterns

Strategy: validation

Validate before calling

// Clamp the crop rect to the asset's real dimensions before submit.
const assetWidth = asset.exifInfo?.width;
const assetHeight = asset.exifInfo?.height;
const x = Math.max(0, Math.min(crop.x, assetWidth - 1));
const y = Math.max(0, Math.min(crop.y, assetHeight - 1));
const width = Math.min(crop.width, assetWidth - x);
const height = Math.min(crop.height, assetHeight - y);
if (x + width > assetWidth || y + height > assetHeight) {
  throw new Error('Crop rect still out of bounds after clamping');
}
await api.put(`/assets/${id}/edits`, { edits: [{ action: 'Crop', parameters: { x, y, width, height } }, ...rest] });

Try / catch

try {
  await api.put(`/assets/${id}/edits`, { edits });
} catch (e) {
  if (e.response?.status === 400 && /out of bounds/i.test(e.response?.data?.message)) {
    edits = clampCropToAsset(edits, asset);
    await api.put(`/assets/${id}/edits`, { edits });
  } else throw e;
}

Prevention

When it happens

Trigger: PUT /assets/{id}/edits with a Crop whose x+width or y+height exceeds the asset's pixel width/height; crop rectangle built against a downscaled preview but submitted against full-resolution dimensions; negative x/y making the rect extend past the edge.

Common situations: Client computes the crop on a preview image but sends coordinates in a different scale than the server's stored dimensions; rounding errors that push width/height one pixel over; stale asset dimensions after a re-encode.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/7c0aa5f328a0e063. Report an issue: GitHub.