Skip to main content
A campaign storefront, called a Säljplats in the Quickbutik admin, is a campaign a merchant runs beside the shop, with its own prices (percent off or fixed), its own quantity limits and its own window of time. Its id is 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.storefrontId and cart.surface say 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 ordinary qb_cart_id cart.
Adding a storefront to an existing integration changes the storage prefix. Carts and sessions remembered under the old qb_… keys are no longer read, so shoppers mid-basket start a fresh campaign cart. That is right for a campaign page and wrong for a shop that only wants attribution. To keep the old keys:
In React, changing 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.
Also exported: isStorefrontId, isStorefrontSurface, DEFAULT_STOREFRONT_SURFACE, STOREFRONT_ID_PATTERN and the StorefrontBinding, StorefrontSurface and StorefrontAttribution types.