Zones
Recommendation blocks the merchant configures in Mercury, rendered by one call: by zone id or by page template, with the cart's contents for cart zones.
A zone is a product block the merchant sets up in the Mercury admin: "You may also like" on the product page, a cart upsell, a row of picks on the home page. The zone decides which products and how to show them; your storefront asks for it and renders what comes back.
A zone has an area (cart, product, collection, cms, wishlist or shortcode), an
appearance (grid, carousel, list, tiles or single) and a mode: automatic (products
from a collection, with rule groups) or manual (products the merchant picked).
Rendering a zone
By id, when you know which zone the merchant placed:
curl "https://mercury.plutocracy.io/zone/query/6f2c...?currency=AUD&bypassToken=$MERCURY_BYPASS_TOKEN"
By template, to let the merchant's configuration choose:
curl "https://mercury.plutocracy.io/zone/query?templateName=product&bypassToken=$MERCURY_BYPASS_TOKEN"
templateName is one of cart, collection, product, cms (or index) and wishlist. If the id
misses, the template is used; anything else falls back to the store's first zone.
The response is the zone (its title, appearance and settings) with products, rendered the same way as
search results: prices in major units, prices narrowed to ?currency=. Collection zones return up to 20
products in the collection's own order, leaving out unavailable products unless the zone allows them;
manual zones return the picked products. query (the filter format)
narrows either kind.
Cart zones
A cart zone's rule groups depend on what is in the cart ("if the cart has a camera, suggest lenses"). Send
the cart lines in a POST body with the same URL:
const cart = await (await fetch('/cart.js')).json();
const res = await fetch('https://mercury.plutocracy.io/zone/query?templateName=cart', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ lines: cart.items.map((item) => ({ handle: item.handle })) }),
});
const zone = await res.json(); // 201 Created: the same payload as a GET
Only lines[].handle is read. The POST answers 201 rather than 200; treat any 2xx as success.
When there is nothing to show
Zones are built to never break a page:
- If the zone can't be resolved for any reason, you get 200 with a fallback:
{ "title": "", "products": [...] }, twelve in-stock products. Render it or hide the block whentitleis empty, as suits the page. - When the matching rule group is set to show nothing, the body is empty (a 2xx with no JSON). Check for an empty body before parsing.
const text = await res.text();
const zone = text ? JSON.parse(text) : null;
if (!zone || zone.products.length === 0) hideBlock();