Back to Spree

Media

docs/developer/core-concepts/media.mdx

5.4.26.2 KB
Original Source

Overview

Spree handles uploads, processing, and delivery for product media. Images are automatically converted to WebP format and preprocessed into multiple sizes for optimal performance.

Product Media

Product media uses the Spree::Asset model, which provides:

  • Multiple media per product with ordering
  • Media typesimage, video, external_video
  • Alt text for accessibility and SEO
  • Focal point coordinates for smart cropping
  • Preprocessed named variants for fast delivery

Each media item has a media_type field that defaults to image. The variant_ids field indicates which product variants the media is associated with — an empty array means the media belongs to the product as a whole.

<Note> `Spree::Image` is a backward-compatible subclass of `Spree::Asset` that sets `media_type` to `image`. New code should use `Spree::Asset` directly. </Note>

Named Variant Sizes

When an image is uploaded, Spree automatically generates optimized versions in the background:

NameDimensionsUse Case
mini128x128Thumbnails, cart items
small256x256Product listings, galleries
medium400x400Product cards, category pages
large720x720Product detail pages
xlarge2000x2000Zoom, high-resolution displays

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.

<CodeGroup>
typescript
// List products — thumbnail_url is always included
const { data: products } = await client.products.list({ limit: 12 })

products.forEach(product => {
  product.thumbnail_url // "https://cdn.../tote-front.webp" — no expand needed
})
bash
# thumbnail_url is always in the response — no ?expand needed
curl 'https://api.mystore.com/api/v3/store/products?limit=12' \
  -H 'Authorization: Bearer pk_xxx'
</CodeGroup> <Warning> 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. </Warning>

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:

<CodeGroup>
typescript
const product = await client.products.get('spree-tote', {
  expand: ['media', 'variants'],
})

// Product media gallery
product.media // [{ id, media_type, product_id, variant_ids, original_url, mini_url, ..., alt, position }, ...]

// Each variant has its own thumbnail and media_count
product.variants?.forEach(variant => {
  variant.thumbnail    // "https://cdn.../tote-red.webp" — always available
  variant.media_count  // 3 — quick check without loading media
  variant.media        // full media array (only with ?expand=media)
})
bash
curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=media,variants' \
  -H 'Authorization: Bearer pk_xxx'
</CodeGroup>

Response (media object):

json
{
  "id": "media_k5nR8xLq",
  "media_type": "image",
  "product_id": "prod_86Rf07xd4z",
  "variant_ids": ["variant_m3Rp9wXz"],
  "position": 1,
  "alt": "Front view",
  "focal_point_x": null,
  "focal_point_y": null,
  "external_video_url": null,
  "original_url": "https://cdn.example.com/images/original.jpg",
  "mini_url": "https://cdn.example.com/images/mini.webp",
  "small_url": "https://cdn.example.com/images/small.webp",
  "medium_url": "https://cdn.example.com/images/medium.webp",
  "large_url": "https://cdn.example.com/images/large.webp",
  "xlarge_url": "https://cdn.example.com/images/xlarge.webp"
}

Media Fields Summary

FieldAvailable onAlways ReturnedDescription
thumbnail_urlProductYesURL to the product's first media
thumbnailVariantYesURL to the variant's first media
media_countVariantYesNumber of media items (counter cache)
mediaProduct, VariantNoFull media array (requires ?expand=media)

Media Object Fields

FieldTypeDescription
idstringPrefixed ID (media_xxx)
media_typestringimage, video, or external_video
product_idstring | nullOwning product prefixed ID
variant_idsstring[]Associated variant prefixed IDs (empty = product-level)
positionnumberSort order
altstring | nullAlt text
focal_point_xnumber | nullHorizontal focal point (0.0–1.0)
focal_point_ynumber | nullVertical focal point (0.0–1.0)
external_video_urlstring | nullExternal video URL (YouTube/Vimeo)
original_urlstring | nullFull-size image URL
mini_url ... xlarge_urlstring | nullNamed variant URLs

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:

ServicePurposeExamples
Public storageProduct images, logos, taxon imagesS3 public bucket, CDN
Private storageCSV exports, digital downloadsS3 private bucket
<Info> For production deployments, use cloud storage (S3, GCS, Azure) instead of local disk storage. See [Asset Deployment](/developer/deployment/assets) for configuration details. </Info>

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