sf_ plus a 26-character Crockford ULID (sf_01JBQ8ZK4M7XW9YR2TCVN3H5PD) and is shown on the campaign in the admin.
Campaign support arrived in kit 1.3.0; the storefrontId shorthand and campaign-priced product reads in 1.4.0.
Binding a storefront
Bind once and every product read, cart and checkout follows the campaign:storefrontId is exactly storefront: { id } with the default surface. Both may be given only when they name the same campaign (case-insensitive); two different ids throw a ShopkitConfigError. null and "" mean “not set”, so storefrontId={campaign?.id ?? null} works. With the elements, configure({ storefrontId }) binds the whole page.
From then on:
- Every product read is priced for the campaign.
products.list(),search(),get()and everything built on them send the id, so the catalog shows what the cart will charge. - Every cart the client creates is bound to the campaign and priced with its overrides on every read (
cart.storefrontIdandcart.surfacesay so). - Every checkout names the campaign, so the platform applies the live-guard and quantity limits, prices the session for it and records it on the order.
- The storage prefix defaults to
qb_<id lower-cased>, so the campaign cart never mixes with the shop’s ordinaryqb_cart_idcart.
storefrontId rebuilds the client and the cart store, so moving from one campaign page to another does not keep showing the first campaign’s basket. A prebuilt client prop can only confirm its own binding; one bound elsewhere throws. On <qb-shop>, storefront-id is observed the same way.
Product reads: per-call storefrontId
Reads are stateless, so any campaign may be named on one, bound client or not:
useProducts and useProductSearch take it as a parameter and useProduct(id, { storefrontId }) as an option.
Carts and checkouts: where a per-call id is allowed
Which campaign a call may name depends on whether the call owns its cart:
The last row is strict because those calls reuse the remembered cart, which was created for one campaign or for none. On an unbound client a mismatch would otherwise create a campaign-priced cart and remember it as the shop’s ordinary cart for thirty days. The two patterns that work:
storefrontId and surface travel together or not at all. A surface with no storefront is dropped, and with no storefront from either source the request is exactly what it was before campaigns existed.
Surfaces
surface is attribution, not behaviour: it records where the campaign was presented. The vocabulary is exported as STOREFRONT_SURFACES:
A malformed id or an unknown surface throws a
ShopkitConfigError at createShopkitClient time. Whether the campaign exists and is open is only known at checkout.
Handling refusals
A campaign checkout can be refused with a machine-readable reason.storefrontRefusal(error) decodes it and returns null for anything else, so it is safe on whatever was thrown:
Campaign prices survive only on checkout-v2. The legacy checkout re-prices every line from the catalog, so a shop that sells on campaign pricing needs checkout-v2.
isStorefrontId, isStorefrontSurface, DEFAULT_STOREFRONT_SURFACE, STOREFRONT_ID_PATTERN and the StorefrontBinding, StorefrontSurface and StorefrontAttribution types.