- Owner
- justin
- Reviewed
- 2026-10-08
- Component
- storefront-api
- Describes
- src/images.ts
Each product has at most one image. It lives in the storefront-images R2 bucket, and the product row keeps its key in image_key. The API turns the key into image_url on the bucket's custom domain, IMAGES_URL in wrangler.jsonc (imageUrls in src/images.ts).
Upload
PUT /admin/products/:id/image takes the raw bytes with a content-type of JPEG, PNG, WebP or AVIF, up to 5 MiB. The key is products/<id>/<hash>.<ext>, where the hash is the first 8 bytes of the SHA-256 of the image. Because the key depends on the content:
- a new image always gets a new URL, so nothing has to be purged from a cache;
- the object can be stored with
cache-control: public, max-age=31536000, immutable; - uploading the same file twice is harmless.
The previous object is left in the bucket. A cart page or a CDN may still hold its URL, and images are small enough that keeping them costs little.
Serving
Browsers load images straight from the bucket's custom domain. R2 answers with the headers stored at upload, and Cloudflare's cache in front of the domain serves repeat requests, so image traffic does not run storefront-api at all (0008 Serve images from the bucket's custom domain). Until October 2026 the Worker served them at GET /images/<key> (0002 Serve images through storefront-api); that route is gone.
The bucket is public on that domain only, so nothing but product images should be written to it.