Products and collections from Commerce Orbit
How Product, Products and Collection fields and the Commerce Orbit blocks are delivered, and how to render them.
Reference4 min read
A space connected to Commerce Orbit under Configuration › Orbit connections has three more field types and four blocks. The entry stores only what was picked, { "id", "name" }. Every answer from the delivery API, the SDK and GraphQL fills in the rest from Commerce Orbit's Storefront API, kept for at most a minute, and fresh in preview.
A product
"product": { "id": "6b0000000000000000000001", "slug": "fjell-gtx-boot", "name": "Fjell GTX boot", "summary": "A waterproof leather boot for long days on rough ground.", "image": { "url": "https://cdn.northwilds.com/fjell-gtx.webp", "alt": "Fjell GTX boot", "mimeType": "image/webp" }, "images": [], "price": 18900, "priceMax": 18900, "compareAtPrice": null, "currency": "EUR", "available": true, "inStock": true, "stock": 14, "variants": [{ "id": "v-42", "title": "EU 42", "options": ["42"], "sku": "FJELL-42", "price": 18900, "compareAtPrice": null, "available": true, "quantity": 3 }], "url": "https://app.bifrostorbit.com/commerce/shop/northwilds/products/fjell-gtx-boot", "addToCartUrl": "https://app.bifrostorbit.com/commerce/shop/northwilds/cart?add=v-42"}- Prices are in minor units, such as cents:
18900is €189.priceis the lowest variant price andpriceMaxthe highest. currencyis the page's region's currency when the shop sells in it, and the shop's own currency otherwise.available: falsemeans the product is not on sale now, or Commerce Orbit could not be reached and had never answered. The picked name is kept so the page still reads well; hide the price and the button.inStocksays whether any variant can be bought now.stockis the total left when every variant counts its stock, andnullotherwise. Each variant has its ownavailableandquantity.addToCartUrladds the first variant that can be bought to the cart on the hosted shop, and isnullwhen the product is sold out. To build your own cart, use the variant ids with Commerce Orbit's Storefront API.
A collection
A Collection field arrives as { id, slug, name, description, image, productCount, url, available, products }, with up to 24 of its products in the shape above.
The blocks
commerceProductCardproduct, heading, buttonLabelcommerceProductGridheading, intro, products, collection, limit, and items: the picked products, or the collection's products when none are picked, up to limitcommerceAddToCartlabel, productcommerceCollectionListheading, intro, limit, and collections: every collection in the shop, without their products| Block | Key | Fields it arrives with |
|---|---|---|
| Product card | commerceProductCard | product, heading, buttonLabel |
| Product grid | commerceProductGrid | heading, intro, products, collection, limit, and items: the picked products, or the collection's products when none are picked, up to limit |
| Add to cart button | commerceAddToCart | label, product |
| Collection list | commerceCollectionList | heading, intro, limit, and collections: every collection in the shop, without their products |
Render the grid from items, so your component never has to choose between the two sources:
import { commercePrice, type CmsCommerceProduct } from '@bifrost-orbit-cms/sdk'import type { BlockProps } from '@bifrost-orbit-cms/sdk/react'export function ProductGrid({ block }: BlockProps<'commerceProductGrid'>) { const items = (block.items ?? []) as CmsCommerceProduct[] return ( <section data-cms-id={block.id}> {block.heading ? <h2>{block.heading}</h2> : null} <ul> {items.filter((product) => product.available).map((product) => ( <li key={product.id}> <a href={product.url ?? '#'}>{product.name}</a> {commercePrice(product)} {product.inStock ? null : ' Sold out'} </li> ))} </ul> </section> )}Refreshing pages
When a product is published, taken off sale, changed or its stock changes in Commerce Orbit, CMS Orbit sends content.published with reason: "commerce" for every live page that shows it, so createRevalidateHandler refreshes those pages. A change to a collection refreshes the pages that show it and those with a Collection list. Pages with a Collection field also refresh when any product changes, since the product may be in the collection.
Prices for signed-in trade customers
Delivered pages carry the shop's normal prices, the same for every visitor, so they can be cached. A trade customer signed in to the shop has prices of their own, from their price list or live from their MAM account. Lay those over the page in the visitor's browser, after it has loaded:
'use client'import { commercePrice, type CmsCommerceProduct, type CommercePersonalPrices } from '@bifrost-orbit-cms/sdk'import { useCommercePrices } from '@bifrost-orbit-cms/sdk/react'const load = async (productIds: string[]): Promise<CommercePersonalPrices | null> => { const response = await fetch('/api/shop/prices', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ productIds }) }) return response.ok ? (await response.json()).data : null}export function Prices({ items, signedIn }: { items: CmsCommerceProduct[]; signedIn: boolean }) { const { products } = useCommercePrices(items, signedIn ? load : null) return products.map((product) => <p key={product.id}>{commercePrice(product)} {product.priceSource === 'account' ? 'Your price' : null}</p>)}/api/shop/prices is a route on your own server that passes the call on to Commerce Orbit with the customer's token from an httpOnly cookie, so the token never reaches the browser. One call covers every product on the page. See Customer accounts and personal prices for signing customers in and for the route.
Was this page helpful?