Overview
Spree handles image uploads, processing, and delivery for product images and other assets. Images are automatically converted to WebP format and preprocessed into multiple sizes for optimal performance.
Product Images
Product images use the Spree::Image model, which provides:
- Multiple images per variant with ordering
- Alt text for accessibility and SEO
- Position management for image galleries
- Preprocessed named variants for fast delivery
Images belong to variants, not products directly. Product-level images are attached to the master variant.
Named Variant Sizes
When an image is uploaded, Spree automatically generates optimized versions in the background:
| Name | Dimensions | Use Case |
|---|
mini | 128x128 | Thumbnails, cart items |
small | 256x256 | Product listings, galleries |
medium | 400x400 | Product cards, category pages |
large | 720x720 | Product detail pages |
xlarge | 2000x2000 | Zoom, 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 an image_count counter.
// 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
})
Avoid using ?expand=images on listing pages. This loads all images for every product in the response. Use thumbnail_url instead and only expand full images on product detail pages.
Full Images (On Demand)
On the product detail page, expand images and variants to get the full set of images with all named variant URLs:
const product = await client.products.get('spree-tote', {
expand: ['images', 'variants'],
})
// Product-level images (from master variant)
product.images // [{ url, mini_url, small_url, medium_url, large_url, xlarge_url, alt, position }, ...]
// Each variant has its own thumbnail and image_count
product.variants?.forEach(variant => {
variant.thumbnail // "https://cdn.../tote-red.webp" — always available
variant.image_count // 3 — quick check without loading images
variant.images // full image array (only with ?expand=images)
})
Response (image object):
{
"id": "img_k5nR8xLq",
"position": 1,
"alt": "Front view",
"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"
}
Image Fields Summary
| Field | Available on | Always Returned | Description |
|---|
thumbnail_url | Product | Yes | URL to the product’s first image |
thumbnail | Variant | Yes | URL to the variant’s first image |
image_count | Variant | Yes | Number of images (no extra query) |
images | Product, Variant | No | Full image array (requires ?expand=images) |
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:
| Service | Purpose | Examples |
|---|
| Public storage | Product images, logos, taxon images | S3 public bucket, CDN |
| Private storage | CSV exports, digital downloads | S3 private bucket |
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 images 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