Images & Responsive Media

Every image uploaded to Shopify — product photos, theme assets — is served from Shopify's CDN, which can resize an image to a huge range of dimensions on the fly just by changing the requested URL. A well-built theme leans on this instead of shipping one large image to every device regardless of screen size.

Requesting a specific size

The image_url filter (the modern replacement for the older img_url) takes a width and/or height and returns a URL for that exact rendition:

Liquid a fixed-size image
<img src="{{ product.featured_image | image_url: width: 600 }}" alt="{{ product.featured_image.alt }}">

Nothing needs to be pre-generated for this to work — Shopify's CDN produces and caches the requested size the first time it's asked for, from the single original file a merchant uploaded.

srcset and responsive images

A fixed size wastes bandwidth on a small phone screen and looks soft on a large retina display. srcset lets the browser choose the best-fitting image from a list of candidates, based on its own screen size and pixel density:

Liquid a responsive image
<img
  src="{{ product.featured_image | image_url: width: 600 }}"
  srcset="
    {{ product.featured_image | image_url: width: 300 }} 300w,
    {{ product.featured_image | image_url: width: 600 }} 600w,
    {{ product.featured_image | image_url: width: 1200 }} 1200w
  "
  sizes="(min-width: 990px) 50vw, 100vw"
  alt="{{ product.featured_image.alt }}"
  loading="lazy"
  width="600"
  height="600"
>

The sizes attribute tells the browser how much of the viewport the image will actually occupy at different breakpoints, which it needs in order to pick the right candidate from srcset before the CSS has even been applied.

The image_tag filter

Writing that full srcset block by hand for every image gets repetitive, so Liquid provides image_tag, which generates a complete, responsive <img> element — including srcset, sizes, width, height, and alt text — from a single filter call:

Liquid image_tag
{{ product.featured_image | image_tag: sizes: '(min-width: 990px) 50vw, 100vw', widths: '300,600,1200' }}

Lazy loading and explicit dimensions

The native loading="lazy" attribute defers loading offscreen images until the user scrolls near them — a meaningful performance win on long product-grid or collection pages. Explicit width and height attributes matter for a different reason: they let the browser reserve the correct space before the image has actually downloaded, preventing the page from visibly jumping around as images load in (a metric called Cumulative Layout Shift).

Video and other media types

Product media isn't limited to images — product.media can also include hosted video and 3D models, each with its own media_type. A template typically checks that type before deciding how to render a given media item:

Liquid handling mixed media types
{% for media in product.media %}
  {% case media.media_type %}
    {% when 'image' %}
      {{ media | image_url: width: 600 }}
    {% when 'video' %}
      {{ media | media_tag }}
  {% endcase %}
{% endfor %}
Note: requesting an image size larger than the source file's actual resolution just returns the original at its native size — Shopify's CDN will never upscale an image beyond what was uploaded.