How to apply Sanity image crop and hotspot data to urlForImage?

1 replies
Last updated: Jun 9, 2023
Using the urlForImage works fine, but now I want to display the cropped image the way it is showing on the back-end the same on the front-end side.
from my GROQ query I got these values:

  crop: {
    right: 0.13602941176470584,
    top: 0,
    left: 0.07720588235294118,
    bottom: 0.5092941176470588,
    _type: 'sanity.imageCrop'
  },
  hotspot: {
    height: 0.41235294117647076,
    _type: 'sanity.imageHotspot',
    width: 0.6764705882352942,
    x: 0.44852941176470607,
    y: 0.23066176470588223
  },
To display the image I do this (with props) which returns 400 bad request. The image is by default 1280 x 1024.

Am i Doing something wrong here?


"url" parameter is valid but upstream response is invalid


const ImageURL = urlForImage(src)
    .width(width)
    .height(height)
    .format("webp")
    .rect(0.07720588235294118, 0, 0.8455882352941176, 1)
    .quality(quality)
    .url()
AI Update

I see the issue! The .rect() method in the image URL builder expects pixel values, not normalized values between 0 and 1. The crop data you're getting from your GROQ query is in normalized format (0-1), but you need to convert it to actual pixel coordinates.

Here's what's happening: when you pass rect(0.07720588235294118, 0, 0.8455882352941176, 1), the image URL builder is trying to create a rectangle starting at pixel 0.077, which doesn't make sense and causes the upstream error.

The Solution

You need to calculate the actual pixel values from the normalized crop values. Here's how to do it:

const ImageURL = urlForImage(src)
  .width(width)
  .height(height)
  .format("webp")
  .rect(
    Math.round(crop.left * 1280),      // left in pixels
    Math.round(crop.top * 1024),       // top in pixels
    Math.round((1 - crop.left - crop.right) * 1280),  // width in pixels
    Math.round((1 - crop.top - crop.bottom) * 1024)   // height in pixels
  )
  .quality(quality)
  .url()

In your case with the values you provided:

  • Left: 0.07720588235294118 * 1280 = 99 pixels
  • Top: 0 * 1024 = 0 pixels
  • Width: (1 - 0.07720588235294118 - 0.13602941176470584) * 1280 = 1008 pixels
  • Height: (1 - 0 - 0.5092941176470588) * 1024 = 502 pixels

Better Approach: Let the URL Builder Handle It

Actually, there's an even simpler solution! When you pass the full image object (with crop and hotspot data) to urlForImage(), the image URL builder should automatically respect the crop and hotspot. Just make sure you're querying the complete image object:

"image": {
  "asset": image.asset->,
  "crop": image.crop,
  "hotspot": image.hotspot
}

Then simply use:

const ImageURL = urlForImage(image)  // Pass the whole image object
  .width(width)
  .height(height)
  .format("webp")
  .quality(quality)
  .url()

The URL builder will automatically apply the crop and center on the hotspot when you provide width/height constraints. This is the recommended approach as described in the @sanity/image-url documentation.

If you still need manual control over the rect, remember: rect parameters are in pixels, not normalized values (left, top, width, height - all in actual pixel dimensions).

Show original thread
1 reply
You’re passing the values directly from
crop
into the
rect
method, but it expects pixels .

Here’s how the image-url builder does it .

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?