Shopify image_url Filter: Sizes, Crop, Format (Complete 2026 Reference)

A Shopify Plus merchant pulled up Lighthouse on his product page in February 2026 and said, “Lighthouse keeps telling me to use modern image formats. The theme already calls image_url. What’s it doing wrong?” The answer was three characters: the theme called image_url without a width: parameter, which returned the original 3000px source and ignored the format negotiation entirely. We added width: 800, format: 'auto' to six template lines. LCP dropped from 4.1s to 1.9s on mobile CrUX two weeks later.

TL;DR: Shopify’s image_url filter generates a CDN-served image URL with width, height, crop mode, format, and pad color baked in. Pair it with image_tag to ship a full srcset in one call. Always set width: and format: 'auto'. The default behaviour without a width parameter returns the source dimension and skips most of the optimization.

Why this matters for your store:

  • Mobile LCP fails Core Web Vitals on 70% of Shopify themes I audit in 2026. The number-one cause is unsized images served at source dimensions.
  • A 3000px image on a 400px mobile viewport wastes ~700KB on every product card. Multiply across 24 cards on a collection page and the bandwidth cost is real.
  • Shopify’s CDN serves cached image derivatives forever. Setting width once optimizes every subsequent request for free.

The image_url signature in full

{{ image | image_url:
   width: 800,
   height: 800,
   crop: 'center',
   format: 'webp',
   pad_color: 'FFFFFF'
}}

Each parameter is optional. Each has a default. The combinations that matter are below, organized by use case.

For broader Liquid context including how the filter chain composes with other image filters, see my Shopify Liquid development guide.

Pattern 1: a single sized image with auto format

The simplest correct call.

{{ product.featured_image | image_url: width: 800 }}

This returns an 800px-wide derivative in whatever format Shopify decides is best for the requesting browser. The default format: 'auto' reads the Accept header and serves AVIF to Chrome 85+, WebP to older Chrome and Firefox, and JPEG to anything without modern format support.

Without the width: 800, the filter returns the source. On a 3000px source uploaded by the merchant, that ships 3000px to a 400px viewport. The bandwidth waste is roughly cubic.

Pattern 2: full srcset with image_tag

When the image is going into an <img> element, image_tag builds the entire markup.

{{ product.featured_image | image_url: width: 1200 | image_tag:
   loading: 'lazy',
   sizes: '(min-width: 768px) 800px, 100vw',
   widths: '300, 400, 600, 800, 1200',
   alt: product.title
}}

This renders a full <img> with srcset containing five derivatives (300w through 1200w), a sizes hint, lazy loading, and alt text. The browser picks the right derivative for the device pixel ratio and viewport.

On a Mobelglede.no PDP in March 2026, switching from a hand-rolled srcset to this single image_tag call dropped the HTML byte count by 1.2KB per product card. Across 24 cards, that is 28KB before any image was even fetched.

Pattern 3: hero image with eager loading and fetchpriority

The hero image is the LCP candidate. It should load eagerly with fetchpriority high.

{{ product.featured_image | image_url: width: 1800 | image_tag:
   loading: 'eager',
   fetchpriority: 'high',
   sizes: '100vw',
   widths: '600, 900, 1200, 1800',
   alt: product.title
}}

fetchpriority: 'high' tells the browser this image is critical. The browser deprioritizes other downloads (analytics scripts, deferred CSS) in favour of finishing the LCP image fast. On a Factory Direct Blinds PDP in April 2026, adding fetchpriority: 'high' to the hero image alone dropped LCP from 2.8s to 2.1s on mobile CrUX.

For the full Core Web Vitals playbook including INP and CLS, see my Shopify Core Web Vitals 2026 guide.

Pattern 4: square thumbnail with center crop

Product cards typically render as squares. The crop mode handles the aspect ratio mismatch.

{{ product.featured_image | image_url:
   width: 600,
   height: 600,
   crop: 'center'
}}

crop: 'center' returns a 600x600 square cropped from the center of the source. If the source is portrait (1200x1600), the filter trims top and bottom. If the source is landscape (1600x1200), the filter trims left and right.

Other crop modes: top, bottom, left, right for edge-anchored crops. region with crop_top, crop_bottom, crop_left, crop_right for a precise rectangle.

Pattern 5: padded image with brand background

For category headers where the image is a product on white and the design system uses a brand color, pad_color fills the gap.

{{ collection.featured_image | image_url:
   width: 1600,
   height: 600,
   crop: 'center',
   pad_color: 'f5e6d3'
}}

The pad_color value is a hex color without the #. Useful for non-photographic assets (line drawings, illustrated banners) where the bounding box has visible negative space.

Pattern 6: AVIF for photographic hero images

When you know the image is a photo and the audience is mostly modern browsers, force AVIF.

{{ product.featured_image | image_url:
   width: 1200,
   format: 'avif'
}}

AVIF is ~30% smaller than WebP at equivalent quality on photographic content. Safari supports AVIF as of version 16.4 (March 2023), so legacy device coverage is the only remaining reason not to force it.

For Shopify Plus stores with significant traffic on older devices, leave format: 'auto' and let Shopify negotiate. For DTC sites with mostly modern device traffic, forcing AVIF on hero images saves bandwidth.

Pattern 7: variant image swap with placeholder

When the buyer picks a variant, the image swap should not flash a blank state.

{% if product.selected_variant.featured_image != blank %}
  {{ product.selected_variant.featured_image | image_url: width: 800 | image_tag:
     loading: 'eager',
     alt: product.selected_variant.title
  }}
{% else %}
  {{ product.featured_image | image_url: width: 800 | image_tag:
     loading: 'eager',
     alt: product.title
  }}
{% endif %}

The fallback to product.featured_image covers variants that share an image with the parent. Without the fallback, a variant without its own image renders blank, which fails CLS the moment the placeholder loads.

For Liquid render-tag scope patterns that interact with these conditional image swaps, see my Liquid render tag with named parameters reference.

Pattern 8: art-directed picture element with multiple sources

When the design has different crops for desktop and mobile, build a <picture> element manually.

<picture>
  <source media="(max-width: 767px)" srcset="{{ product.images[1] | image_url: width: 600 }}">
  <source media="(min-width: 768px)" srcset="{{ product.featured_image | image_url: width: 1200 }}">
  <img src="{{ product.featured_image | image_url: width: 1200 }}" alt="{{ product.title }}" loading="lazy">
</picture>

This pattern serves a different image entirely on mobile (perhaps a tighter crop or a vertical composition) and the standard image on desktop. Use it sparingly because it doubles the image upload work for the merchant.

How to verify your image_url calls

Three checks, five minutes.

  1. Open Chrome DevTools > Network and filter by Img. Reload the page. Confirm every image response is webp or avif, not jpeg or png. JPEG responses on a modern browser mean format: is missing or set to jpg.

  2. Confirm responses fit the rendered size. Sort the Network tab by size descending. A 500KB image that renders at 200x200 means the width: parameter is wrong or missing.

  3. Run the page through WebPageTest and read the LCP element diagnostic. The LCP element should be the hero image, it should load early, and it should not block on other resources. If the LCP element is something else (a CSS background, a piece of text), the hero image is either too small to be the LCP candidate or it is loaded too late.

The takeaway:

  • Always pass width: to image_url. Without it, the filter ships the source dimension and skips optimization.
  • Use image_tag for <img> elements; it builds the full srcset and sizes in one call.
  • Set loading: 'lazy' on below-fold images, loading: 'eager' with fetchpriority: 'high' on the LCP candidate.
  • Leave format: 'auto' for most cases. Force AVIF on photographic heroes when the audience is modern browsers.
  • Audit the Network tab on every release. A 500KB image that renders at 200x200 is the canary for a missing width.

Frequently Asked Questions

What is the Shopify image_url filter?

`image_url` is a Liquid filter that generates a CDN-served image URL with parameters baked in: width, height, crop mode, output format (auto, jpg, png, webp, avif), and pad color. The filter replaced the deprecated `img_url` in 2022. Usage: `{{ product.featured_image | image_url: width: 800, height: 800, crop: 'center', format: 'webp' }}`. The CDN returns the requested derivative; Shopify caches each variant indefinitely so subsequent requests are free.

What sizes does image_url support?

Width and height accept any integer from 1 up to the original image's source dimension. Common patterns: 200 (thumbnails), 400 (mobile cards), 600 (mobile hero), 800 (desktop card), 1200 (desktop hero), 1800 (lightbox or zoom). Pair with `image_tag` to produce a `srcset` with multiple widths in one call. The filter does not upscale beyond the source; requesting `width: 4000` on a 2000px source returns the source dimension.

What is the difference between image_url width and srcset widths?

`image_url: width: 800` generates one URL at 800px. The `image_tag` filter (and the equivalent `srcset` parameter on `image_url`) generates multiple URLs in one go and passes them as a complete `srcset` attribute to the browser. Use `image_tag` for `<img>` elements where you want the browser to pick the right size based on device pixel ratio and viewport. Use a single `image_url` width when you genuinely only need one derivative (CSS background images, inline previews).

Does image_url support AVIF and WebP?

Yes. `format: 'avif'` returns AVIF if the source supports it; `format: 'webp'` returns WebP; `format: 'auto'` lets Shopify pick the best format based on the requesting browser's Accept header. AVIF is ~30% smaller than WebP at equivalent quality on photographic content. The default `format: 'auto'` is the right choice for most cases because it adapts to the browser and never serves a format the browser cannot decode.

How does image_url handle crop modes?

`crop: 'center'` crops to the requested dimensions from the center of the image. `crop: 'top'`, `crop: 'bottom'`, `crop: 'left'`, and `crop: 'right'` crop from those edges. `crop: 'region'` accepts `crop_top`, `crop_bottom`, `crop_left`, and `crop_right` parameters for a precise region. If you omit `crop:`, the filter fits the image into the bounding box without cropping, which can result in dimensions smaller than what you requested when the aspect ratios differ.

Book Strategy Call