Skip to main content

Images: render url, never path

image.path is a bare filename like 5c7b0e7e1802c.jpeg. Completing it needs the shop’s storage prefix, which no storefront key can read, so <img src={image.path}> resolves against your own origin and 404s. This is the most common way a headless Quickbutik shop ships broken images.
url deliberately carries no resize parameters, because only the renderer knows the size. The Quickbutik image CDN takes imgix-compatible parameters, and the kit builds them:

Helpers

Transform options: width (w), height (h), dpr, quality (q), format (fm), fit, crop, background (bg), and auto, which defaults to "format" (pass null to drop it).

Behaviours worth knowing

  • Images still processing are skipped. A freshly uploaded image exists before its file does, marked with contentHash: "temp". Pass includePending: true to include it.
  • contentHash is appended as ?v=, so a replaced image is not served stale. Pass cacheBust: false to turn that off.
  • null instead of a broken URL. Render your own placeholder. <ProductImage> renders its fallback (nothing by default) and <qb-product-image> gets an empty attribute.
  • Only if you front the CDN yourself, set imageBaseUrl on the client to the shop-scoped base without the products/ segment, and the helpers complete path with it.
  • If you use next/image, add the CDN host (cdn.quickbutik.com) to images.remotePatterns. Never use a wildcard pattern, which turns /_next/image into an open proxy.

Variant images

variant.imageId is the id of one of the product’s own images, or null when the variant has none. Swap the gallery when the shopper picks a variant, falling back to the main image:

Content sections

product.sections are the merchant’s content sections (“Size guide”, “Care”, “Delivery”) in their order: { id, title, content }. content is merchant-authored HTML: sanitize it before injecting it. Sections arrive rendered the way the Quickbutik theme renders them, with template text filled in and the [STOCKLEFT], [PRICE] and [BEFOREPRICE] merge tags replaced. Empty sections are left out.
products.get() (and so getBySlug()) returns relatedProducts: { mode, products }. list() and search() do not. Each entry is a card-sized summary, { id, name, slug, price: { price, comparePrice }, image }, priced like the product list (automatic discounts and campaign prices included). Fetch the full product with products.get(id) when a card is opened.
imageId, sections and relatedProducts are typed from kit 1.5.0. The API serves them to older builds too; read them through a cast there.

Money

Every amount in the kit and the Storefront API is an integer in minor units (öre, cents): 24900 is 249,00 kr. Never a float, never a pre-formatted string. The MinorUnits type alias marks every such field.
formatMoney uses the currency’s real number of decimals, so JPY and ISK (which have none) are not divided by 100. In React, the price hooks hand back raw { amount, currency } (the product’s own currency) and leave formatting to you. The web components format for you (price.display, cart.totalFormatted) because an HTML binding has nowhere else to do it; the raw integers (price.amount, item.lineTotal) are always there too.

Stock

variant.stock.stock is null when the shop does not track inventory (and for preorder items). null means purchasable, not sold out. Only a number at or below zero is out of stock. Never fabricate “only 3 left”.