Skip to main content

Overview

Spree handles uploading, processing and serving the images and video a store shows. Files are converted to WebP and preprocessed into several sizes, so a listing page isn’t downloading full-resolution photographs. Files live in a store-wide media library. A file is uploaded once and can then be placed wherever it’s needed — on a product, on a category, inside a description — without being uploaded again.

The media library

Every file in a store is in the library, whether or not it’s currently placed on anything. That means a file can be uploaded ahead of time, browsed, searched, and reused.

Reuse shares the file, not the record

Placing a library file on a product creates a new media row that shares the same underlying file:
The copy gets its own alt text, its own position, and its own variant links — because the same photograph might be the third image on one product and the hero on another, with different alt text describing what matters in each context. What it doesn’t get is a second copy of the bytes.
This is why there’s no single “shared asset” record to manage. Each placement is independently editable, while storage is shared. Detaching a photo from one product has no effect on any other.

Deleting, safely

A file in use can’t be deleted by accident:
The dashboard shows you the usage list and asks before doing the second one. Check usage before offering a delete in your own tooling.

What can carry media

Categories and collections also have simple image and square_image slots. Setting one places the file; clearing it removes the placement but keeps the file in the library, so nothing is destroyed by tidying up a page. Sellers and stores keep their branding as plain attachments — a logo isn’t merchandising, and it doesn’t belong in a library people browse for product photos.

Product media

A media record carries:
  • Position for ordering within the gallery
  • Media type — image, video, or external_video (defaults to image)
  • Alt text for accessibility and SEO
  • Focal point coordinates for smart cropping
  • Preprocessed named variants for fast delivery
  • variant_ids — which product variants the media represents. An empty array means it represents the product as a whole.
Media belongs to the product, and any subset of its variants can reference the same file through variant_ids — so one photograph can represent three colourways without being uploaded three times.

Uploading a product-level image

Creating media from a remote URL

When the image already lives at a public URL, pass url instead of a signed_id — Spree fetches the remote file and stores it as product media, so you skip the direct-upload step entirely.
The fetch runs in the background, so this request returns 202 Accepted with no body — the media appears in the gallery once the download and processing finish. Re-fetch the product’s media to know when it’s ready.

Sharing a single image across variants

Pass a variant_ids array on the same media endpoint to link/unlink variants. The server replaces the asset’s link set on every call — empty array clears all links, omitting the field leaves them untouched.
Variants belonging to a different product are silently dropped — the API rejects cross-product tampering at the model layer. Reordering happens once on the product gallery; every linked variant inherits the new order. The Store API’s media field on a product returns its gallery — product-level media when present, falling back to legacy variant-pinned images during the transition. On a variant, media returns the assets linked to that variant via variant_ids, falling back to direct variant uploads. This dual rendering means existing storefronts keep working during the upgrade; new uploads attach to the product, and you opt into a one-shot migration to re-home legacy variant-pinned data when convenient.

Video

A product gallery can hold video as well as images. Spree supports both ways merchants usually have it: Spree reads the link once, when it is saved, and rejects anything it cannot embed — so a broken URL is caught at the point a merchant enters it rather than in the storefront. What it derives comes back on the media object: Because the derived fields are on the response, a storefront embeds a video without parsing links itself.

Adding an external video

Uploading a video file

A hosted video is uploaded the same way an image is, with media_type telling Spree what it is. Add poster_signed_id to give it a still frame:
A poster can also be added or replaced later, on its own:
Spree serves an uploaded video as you uploaded it — it does not transcode. Keep files web-friendly (H.264 MP4 or WebM) so they play everywhere, and prefer an external video for long footage so the provider handles streaming.

Posters

A video has no image of its own, so its sized URLs (small_url, large_url, and the rest) resolve to its poster — the still shown before the video plays. A gallery that only knows how to draw an image still renders the right picture, and can play the video when the shopper asks for it. Where the poster comes from, in order:
  1. The one the merchant uploaded — poster_signed_id on write, editable in the dashboard’s media editor.
  2. The provider’s own still, for a YouTube link.
  3. Nothing, for a Vimeo link or an uploaded file with no poster — the tile falls back to a placeholder.
Spree does not extract a frame from an uploaded video, so give hosted video and Vimeo links a poster if you want them to show a still.

Focal Point

focal_point_x and focal_point_y mark the part of an image that must stay in frame when a storefront crops it to a different shape. Both are fractions between 0 and 1, measured from the top left, so { x: 0.5, y: 0.5 } is dead centre — which is also what a storefront should assume when they are null.
Spree stores the focal point and serves it; the cropping itself is the storefront’s decision, since only it knows the shape it needs.

Named Variant Sizes

When an image is uploaded, Spree automatically generates optimized versions in the background: All variants are cropped to fill the exact dimensions and converted to WebP format.

Store API

Thumbnails (Always Available)

Every product response includes a thumbnail_url field — ready to use without any expands. Similarly, each variant includes a thumbnail_url and a media_count counter.
Avoid using ?expand=media on listing pages. This loads all media for every product in the response. Use thumbnail_url instead and only expand full media on product detail pages.

Full Media (On Demand)

On the product detail page, expand media and variants to get the full set of media with all named variant URLs:
Response (media object):
Response

Media Fields Summary

Media Object Fields

The Admin API adds the fields the library needs:
Two behaviours to expect when rendering a gallery:original_url is null for any playable video — there’s no still image to size. Use poster_url.A video’s sized URLs come from its poster. If a video has a poster, every *_url resolves from that frame, so a grid of mixed images and video renders uniformly.

Images in descriptions

Rich text fields — a product description, a category’s copy — can carry embedded images. In the dashboard, the editor opens the media library so a merchant picks an existing file or uploads a new one. Embedded images use their own rendition, sized to fit rather than cropped to a square. A size chart or a diagram keeps its proportions, where a gallery thumbnail would have had its edges cut off.
An embedded image is a plain image URL in the HTML — nothing records which description uses which file.Spree’s usage check does its best by searching descriptions for the file, but it can’t be exhaustive. Deleting a library file can leave a broken image in a description, so treat the usage list as a warning rather than proof a file is unused.

Who can manage media

Media is its own permission, separate from products: The reasoning: reaching a file through a product you can already edit isn’t the same as enumerating every file in the store. Someone who manages one product’s photos shouldn’t automatically be able to browse — or delete — everything the business has ever uploaded. Note the asymmetry that follows. Removing an image from a product removes the placement and needs only product permission; deleting the file everywhere is a library action and needs the media key.

Image processing

Spree uses libvips for image processing. Images are automatically:
  • Converted to WebP format for optimal file size
  • Preprocessed on upload into all named variant sizes
  • Cached for subsequent requests

Storage

Spree supports two storage service types:
For production deployments, use cloud storage (S3, GCS, Azure) instead of local disk storage. See Asset Deployment for configuration details.

Best Practices

  • Use thumbnail_url on listing pages — avoid loading full media via expand
  • Always provide alt text for accessibility and SEO
  • Use named variant sizes (mini, small, medium, large, xlarge) for optimal performance
  • Use a CDN in production for faster delivery
  • Give every video a poster so a gallery has something to show before playback