> ## Documentation Index
> Fetch the complete documentation index at: https://quickbutik.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# @quickbutik/kit

> Generated type reference for the root entry point of @quickbutik/kit 1.8.0: the client, resources, types, storage, images, money, SEO and the variant matrix.

<Info>
  Generated from the typings of `@quickbutik/kit@1.8.0` as published on npm. Do not edit this page by hand: run `npm run generate` in `tools/kit-api-reference`. For guided, example-led documentation see the [API reference](/kit/reference/client).
</Info>

```ts theme={null}
import { … } from "@quickbutik/kit"
```

## Classes

### Analytics

The hub: one `track()` the storefront calls, fanned out to every
destination the shopper's consent allows.

Events are remembered (bounded) rather than dropped when a destination is
not ready for them: before the shopper decides, a `view_item` waits, and is
delivered the moment `marketing` is granted — or forgotten the moment it is
denied. The same memory covers destinations that arrive late, which is the
normal case: `destinationsFromShop()` needs one request to learn the
merchant's ids, and the product page's `view_item` has usually fired by
then. Gating happens at dispatch time, so withdrawing consent stops a
destination immediately even though its script stays on the page.

Purchases are deduplicated across page loads through the platform's own
localStorage key, and carried to destinations with the platform's own Meta
`eventID`, so a thank-you page can be reloaded without counting the order
twice — and the platform's server-side event, when there is one, merges
with the browser's.

#### Constructors

##### Constructor

```ts theme={null}
new Analytics(options?): Analytics;
```

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`AnalyticsOptions`](#analyticsoptions) |

###### Returns

[`Analytics`](#analytics)

#### Properties

##### requireConsent

```ts theme={null}
readonly requireConsent: boolean;
```

False when the hub runs ungated (`requireConsent: false`).

#### Accessors

##### consentState

###### Get Signature

```ts theme={null}
get consentState(): ConsentState | null;
```

The decision the hub gates on; null when it runs ungated. Required
consent with no store to read it from is an undecided shopper, forever —
never null, which every destination reads as "everything granted".

###### Returns

[`ConsentState`](#consentstate-1) | `null`

##### destinations

###### Get Signature

```ts theme={null}
get destinations(): readonly AnalyticsDestination[];
```

The destinations the hub holds, in order.

###### Returns

readonly [`AnalyticsDestination`](#analyticsdestination)\[]

#### Methods

##### addDestination()

```ts theme={null}
addDestination(destination): void;
```

Add a destination after construction — what `destinationsFromShop()`
feeds in once the shop's ids are known. It receives the events the hub
still remembers, consent permitting.

###### Parameters

| Parameter | Type |
| - | - |
| `destination` | [`AnalyticsDestination`](#analyticsdestination) |

###### Returns

`void`

##### allows()

```ts theme={null}
allows(category): boolean;
```

Whether a category may be used right now, by the hub's own rule.

###### Parameters

| Parameter | Type |
| - | - |
| `category` | [`ConsentCategory`](#consentcategory) \| `null` |

###### Returns

`boolean`

##### attachCart()

```ts theme={null}
attachCart(cartStore): () => void;
```

Turn the cart store's mutations into `add_to_cart` / `remove_from_cart`
events — the quantities that changed, not the whole basket — and learn
the shop's currency and numeric store id from it. Both are kept once
seen: a cart that goes away (bought, expired) does not unlearn the shop.
Returns the detach function.

###### Parameters

| Parameter | Type |
| - | - |
| `cartStore` | [`CartStore`](/kit/api/react#cartstore) |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

##### destroy()

```ts theme={null}
destroy(): void;
```

`stop()`, plus forget the cart and the remembered events.

###### Returns

`void`

##### observeCart()

```ts theme={null}
observeCart(cartStore): () => void;
```

Report a second cart's `add_to_cart` / `remove_from_cart` through this
hub, without making it THE cart: the currency and store id are still
learned from the one `attachCart` was given, and attaching does not
replace it. For a nested shop (a campaign storefront inside the site's
provider) that shares the outer hub rather than loading the pixels
twice. Returns the detach function.

###### Parameters

| Parameter | Type |
| - | - |
| `cartStore` | [`CartStore`](/kit/api/react#cartstore) |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

##### removeDestination()

```ts theme={null}
removeDestination(destination): void;
```

Stop delivering to a destination. Its script, if it loaded one, stays
on the page (nothing can unload it); it simply receives nothing more.
Adding the same object back resumes it without a replay.

###### Parameters

| Parameter | Type |
| - | - |
| `destination` | [`AnalyticsDestination`](#analyticsdestination) |

###### Returns

`void`

##### start()

```ts theme={null}
start(): void;
```

Begin delivering: load what consent allows, subscribe to changes.
Idempotent, and `stop()` undoes it, so a React effect can call the pair
on every mount/unmount.

###### Returns

`void`

##### stop()

```ts theme={null}
stop(): void;
```

###### Returns

`void`

##### track()

```ts theme={null}
track(event): void;
```

Record an event and deliver it wherever consent allows.

###### Parameters

| Parameter | Type |
| - | - |
| `event` | [`CommerceEvent`](#commerceevent) |

###### Returns

`void`

##### trackPageView()

```ts theme={null}
trackPageView(params?): void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `params?` | [`CommerceEventParams`](#commerceeventparams-1) |

###### Returns

`void`

##### trackPurchase()

```ts theme={null}
trackPurchase(purchase, key): boolean;
```

Report an order once. Returns false when this browser already reported
it (a reloaded thank-you page) — the event is then not sent again.
`storeId` falls back to the hub's (its option, else the attached
cart's); without any the key is still unique per order, just not shared
with the platform's own surfaces.

###### Parameters

| Parameter | Type |
| - | - |
| `purchase` | [`PurchaseEvent`](#purchaseevent) |
| `key` | \{ `orderNumber?`: `string` \| `number`; `storeId?`: `string` \| `number` \| `null`; } |
| `key.orderNumber?` | `string` \| `number` |
| `key.storeId?` | `string` \| `number` \| `null` |

###### Returns

`boolean`

***

### AsyncResource

One in-flight fetch and its result, observable, with no framework attached.

The non-React twin of `react/use-async.ts`, and the same non-promise: this is
NOT a cache. shopkit ships no data-layer opinion — it exists so the elements
layer can load a product list without the package growing a query library.

A superseded run is aborted and its late response discarded, which is what a
search-as-you-type box needs: `run()` called five times must land the fifth
answer, not whichever server replied last.

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Constructors

##### Constructor

```ts theme={null}
new AsyncResource<T>(initialData?): AsyncResource<T>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `initialData?` | `T` \| `null` |

###### Returns

[`AsyncResource`](#asyncresource)\<`T`>

#### Properties

##### getSnapshot()

```ts theme={null}
getSnapshot: () => AsyncSnapshot<T>;
```

###### Returns

[`AsyncSnapshot`](#asyncsnapshot)\<`T`>

##### subscribe()

```ts theme={null}
subscribe: (listener) => () => void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | () => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

#### Methods

##### abort()

```ts theme={null}
abort(): void;
```

Cancel the in-flight run, if any, without changing the last result.

###### Returns

`void`

##### hydrate()

```ts theme={null}
hydrate(data): void;
```

Adopt a result fetched elsewhere — a server render passing its data in.

###### Parameters

| Parameter | Type |
| - | - |
| `data` | `T` \| `null` |

###### Returns

`void`

##### run()

```ts theme={null}
run(operation): Promise<T | null>;
```

Start (or restart) the operation, cancelling whatever was in flight.

Resolves to the result, or to null when the run failed or was superseded —
errors are captured on the snapshot rather than thrown, so a caller in an
event handler cannot produce an unhandled rejection.

###### Parameters

| Parameter | Type |
| - | - |
| `operation` | (`signal`) => `Promise`\<`T`> |

###### Returns

`Promise`\<`T` | `null`>

***

### CartResource

The shopper's cart.

Two layers live here. The plain methods (`create`, `get`, `addItem`, …) are a
thin, stateless mapping of the Cart API. The `current`/`ensure` pair adds the
one piece of state a storefront always ends up writing itself: remembering
WHICH cart this visitor has, in a way that works the same in the browser, in
a server component and in a server action.

#### Constructors

##### Constructor

```ts theme={null}
new CartResource(
   transport, 
   scopes, 
   store, 
   storefront?, 
   currency?): CartResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `transport` | [`Transport`](#transport) |
| `scopes` | [`ScopeGuard`](#scopeguard) |
| `store` | [`SessionStore`](#sessionstore) |
| `storefront?` | [`StorefrontBinding`](#storefrontbinding) \| `null` |
| `currency?` | `CurrencyPreference` \| `null` |

###### Returns

[`CartResource`](#cartresource)

#### Methods

##### add()

```ts theme={null}
add(item, options?): Promise<Cart>;
```

Add to the remembered cart, creating it on first use.

Recovers once from a cart the server has since dropped: a stale id in a
month-old cookie would otherwise 404 the very first "add to cart" click of
a returning visitor.

###### Parameters

| Parameter | Type |
| - | - |
| `item` | [`AddCartItemInput`](#addcartiteminput) |
| `options?` | [`CartCreateOptions`](#cartcreateoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

##### addItem()

```ts theme={null}
addItem(
   cartId, 
   item, 
options?): Promise<Cart>;
```

Add a product. Adding the same product+variant again increments the
existing line rather than creating a second one, so callers do not have to
look first.

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |
| `item` | [`AddCartItemInput`](#addcartiteminput) |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

##### clear()

```ts theme={null}
clear(options?): Promise<void>;
```

Empty the basket: delete the REMEMBERED cart server-side and forget it
locally, so the next `add()` starts a fresh one.

```ts theme={null}
await shopkit.cart.clear()
```

The call a "Töm varukorgen" button wants, without the id bookkeeping
[delete](#delete) needs. Three cases, none of them an error:

* **No remembered cart** — nothing to clear, so no request is made. A
  visitor who never added anything, or a second click, causes no write.
* **The cart is already gone** (404/410 from the delete — expired, or
  bought in another tab) — the goal was an empty basket and the shopper
  has one, so the stale id is forgotten all the same. Surfacing that as an
  error would leave a button that can never succeed, because the id it
  keeps retrying is the one that no longer resolves.
* **Any other failure** (a network error, a 5xx) throws, and the id is
  KEPT: the cart may well still exist server-side, and forgetting it would
  strand those items in a cart this visitor can no longer reach.

The checkout session id, if one is remembered, is left alone — a
thank-you page still needs it to confirm an order already paid for.

`fallbackCartId` is the cart to delete when NONE is remembered — a cart
rendered from a server read that this browser's storage does not hold.
A remembered id always wins over it.

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`RequestOptions`](#requestoptions) & `object` |

###### Returns

`Promise`\<`void`>

##### create()

```ts theme={null}
create(options?): Promise<Cart>;
```

Create a new empty cart. Does not remember it — see [ensure](#ensure).

On a client bound to a campaign storefront (or when `storefrontId` is
passed here) the cart is created BOUND to that campaign: the platform
prices it with the campaign's overrides on every read, and a checkout
session created from it inherits the binding. Otherwise the request
carries no body, as it always has. This is the stateless call, so any
campaign may be named — pass the returned `cart.id` as `cartId` to
`checkout.start()` to check it out.

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`CartCreateOptions`](#cartcreateoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

##### current()

```ts theme={null}
current(options?): Promise<Cart | null>;
```

The remembered cart, WITHOUT creating one.

Use this wherever a missing cart is a legitimate answer — a header badge, a
server-rendered cart page — so a crawler or a first-time visitor does not
cause a write. A remembered id that no longer resolves is forgotten and
reported as null.

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1) | `null`>

##### currentId()

```ts theme={null}
currentId(): Promise<string | null>;
```

The remembered cart id, or null when this visitor has none yet.

###### Returns

`Promise`\<`string` | `null`>

##### delete()

```ts theme={null}
delete(cartId, options?): Promise<void>;
```

Delete the cart server-side. Also forgets it locally.

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<`void`>

##### ensure()

```ts theme={null}
ensure(options?): Promise<Cart>;
```

The remembered cart, creating and remembering one if needed.

This is the call that needs writable storage. In a read-only context (an
RSC, which cannot set cookies) the cart is still created server-side and
returned — it just is not remembered, so prefer calling it from a place
that CAN write (a server action, a route handler, the browser).

A cart it has to create is bound to the client's campaign storefront — see
[create](#create). A cart it merely finds is returned as it is, which on a
bound client is a cart this same client created, because a bound client
remembers its cart under its own storage prefix. A per-call `storefrontId`
may only restate the client's binding: this call reuses the remembered
cart, and a different campaign could neither apply to it nor be
remembered safely (see `StorefrontAttribution`).

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`CartCreateOptions`](#cartcreateoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

##### get()

```ts theme={null}
get(cartId, options?): Promise<Cart | null>;
```

Fetch a cart by id, or null when it no longer exists.

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1) | `null`>

##### removeItem()

```ts theme={null}
removeItem(
   cartId, 
   itemId, 
options?): Promise<Cart>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |
| `itemId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

##### updateItem()

```ts theme={null}
updateItem(
   cartId, 
   itemId, 
   quantity, 
options?): Promise<Cart>;
```

Set an absolute quantity on a cart LINE (`item.id`, not the product id).

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |
| `itemId` | `string` |
| `quantity` | `number` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Cart`](#cart-1)>

***

### CategoriesResource

The shop's category tree. Read-only for a storefront credential.

#### Constructors

##### Constructor

```ts theme={null}
new CategoriesResource(transport, scopes): CategoriesResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `transport` | [`Transport`](#transport) |
| `scopes` | [`ScopeGuard`](#scopeguard) |

###### Returns

[`CategoriesResource`](#categoriesresource)

#### Methods

##### get()

```ts theme={null}
get(categoryId, options?): Promise<Category | null>;
```

A single category by prefixed (`cat_12`) or numeric id, or null.

A bare number is prefixed on the way out — the gateway validates the path
param against `^cat_\d+$` and 400s a plain `12`, the same trap
`products.search({ categoryId })` already normalises away.

###### Parameters

| Parameter | Type |
| - | - |
| `categoryId` | `string` \| `number` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Category`](#category) | `null`>

##### list()

```ts theme={null}
list(params?): Promise<Page<Category>>;
```

One page of categories. Pass `root: true` for the top level, or a
`parentId` (prefixed `cat_…`) to walk one level down.

###### Parameters

| Parameter | Type |
| - | - |
| `params?` | [`CategoryListParams`](#categorylistparams) & [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Page`](#page)\<[`Category`](#category)>>

***

### CheckoutResource

Checkout, as a headless storefront uses it: build a session from a cart, hand
the shopper to the hosted Quickbutik checkout, then confirm the order when
they come back.

Payment itself is deliberately NOT part of this surface. The hosted checkout
owns the PSP integration, PCI scope, 3-D Secure, wallets and the payment
retry logic — the storefront owns the catalog, the cart and the thank-you
page.

#### Constructors

##### Constructor

```ts theme={null}
new CheckoutResource(
   transport, 
   scopes, 
   store, 
   config, 
   cart, 
   currency?, 
   consent?): CheckoutResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `transport` | [`Transport`](#transport) |
| `scopes` | [`ScopeGuard`](#scopeguard) |
| `store` | [`SessionStore`](#sessionstore) |
| `config` | [`ResolvedShopkitConfig`](#resolvedshopkitconfig) |
| `cart` | [`CartResource`](#cartresource) |
| `currency?` | `CurrencyPreference` \| `null` |
| `consent?` | [`ConsentResource`](#consentresource) |

###### Returns

[`CheckoutResource`](#checkoutresource)

#### Methods

##### buyNow()

```ts theme={null}
buyNow(
   item, 
   input, 
options?): Promise<BuyNowResult>;
```

Add a product and hand off immediately — the "Buy now" button. Uses the
remembered cart, so an existing basket is carried along rather than
replaced.

Returns the URL rather than navigating, exactly as [start](#start-2) does;
`useCheckout().buyNow` and `<qb-buy-now>` are the navigating wrappers.
The result also carries `cart` — the cart as the add left it — so a
caller that keeps its own cart state (the shared `CartStore` does) can
fold it in without a second read: a page that stays put (`no-redirect`,
a new tab) or is restored from the back/forward cache must not show a
badge that missed the item it just added.

###### Parameters

| Parameter | Type |
| - | - |
| `item` | [`AddCartItemInput`](#addcartiteminput) |
| `input` | [`StartCheckoutInput`](#startcheckoutinput) |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`BuyNowResult`](#buynowresult)>

##### confirmation()

```ts theme={null}
confirmation(sessionId, options?): Promise<SessionConfirmation>;
```

One snapshot of the order-creation status for a handoff.

Order creation is asynchronous: the PSP authorizes, then the platform
builds the order. Between those two moments the status is
`processing_payment` and there is no order number yet.

Takes either handle — a checkout-v2 session id or a legacy order uuid (both
arrive as `StartCheckoutResult.handoffId`); the platform resolves which it
is. Read the statuses with care on a legacy handoff: that checkout leaves a
failed payment looking untouched, so `no_attempt` means "not paid yet", NOT
"the shopper never tried". See `LegacyHandoff`.

###### Parameters

| Parameter | Type |
| - | - |
| `sessionId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`SessionConfirmation`](#sessionconfirmation)>

##### createSession()

```ts theme={null}
createSession(input, options?): Promise<CheckoutSession>;
```

Create a checkout session for a cart and remember it.

`successUrl` may be any `https` URL — `http` only for `localhost`/loopback
— see [CreateSessionInput.successUrl](#successurl-2). The same rule applies to
`cancelUrl` and `backUrl` (where the checkout's "continue shopping" links
point). Only the ORIGIN of `successUrl` is used for the return redirect.

A campaign storefront (`storefrontId` on the input, else the client's
`storefront`) is sent along with its `surface`; the platform then prices
the session with the campaign, refuses with a typed error when the
campaign is not live, a quantity limit is exceeded or the cart belongs to
another campaign (see `storefrontRefusal()`), and puts the campaign on
the order. With neither set, nothing is sent and a cart that was created
bound to a campaign passes its own binding on server-side. Because the
caller names the cart here, any campaign may be named.

###### Parameters

| Parameter | Type |
| - | - |
| `input` | [`CreateSessionInput`](#createsessioninput) |
| `options?` | [`RequestOptions`](#requestoptions) & `object` |

###### Returns

`Promise`\<[`CheckoutSession`](#checkoutsession)>

##### currentSessionId()

```ts theme={null}
currentSessionId(): Promise<string | null>;
```

The remembered checkout session id, or null.

###### Returns

`Promise`\<`string` | `null`>

##### embedUrl()

```ts theme={null}
embedUrl(input): string;
```

The URL of the checkout document to FRAME for a session —
`/embed/{storeId}/{sessionId}`.

The sibling of [hostedUrl](#hostedurl), and everything that doc says about
`storeId` applies here verbatim: it is the shop's NUMERIC id
(`Cart.storeId`), not the prefix in the publishable key, and both ids ride
the path because storage is partitioned — or absent — inside a
third-party frame, so the URL has to carry everything needed to rehydrate.

A distinct top-level `/embed/` prefix rather than a path under
`/checkout/`: the two documents are not interchangeable. The embed one is
chrome-less, never navigates itself, and is served with a
`frame-ancestors` header naming the session's recorded embed origin — so a
session created WITHOUT an `embed` block renders a refusal here, whatever
URL you build. [mount](#mount) is the supported way to reach this; build it
by hand only when you are framing the checkout yourself.

###### Parameters

| Parameter | Type |
| - | - |
| `input` | `string` \| [`HostedCheckoutUrlInput`](#hostedcheckouturlinput) |

###### Returns

`string`

##### finalize()

```ts theme={null}
finalize(): Promise<void>;
```

Forget the cart and session ids. Called automatically once a confirmation
comes back `completed`, so the next visit starts a clean basket instead of
resurrecting a cart that has already been bought.

The shop's numeric store id remembered off the cart is kept, here as
everywhere a cart is forgotten (a header badge finding the cart gone,
`cart.clear()`): it is a constant of the shop, not of the order, and it
is what a thank-you page keys the `purchase` event on
(`rememberedStoreId()`). A reload of that page — with the session id on
its URL — has to build the same key as the first visit, or the order is
counted twice. The copy kept next to the checkout session goes with the
session, as documented on `SessionStore`.

###### Returns

`Promise`\<`void`>

##### getSession()

```ts theme={null}
getSession(sessionId, options?): Promise<CheckoutSessionSnapshot | null>;
```

Read a session. Useful on the thank-you page to show what was bought
(`data.cart_products`, `data.order_total`) without a second cart lookup.
Null when the session is unknown or has been cleaned up.

###### Parameters

| Parameter | Type |
| - | - |
| `sessionId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`CheckoutSessionSnapshot`](#checkoutsessionsnapshot) | `null`>

##### hostedUrl()

```ts theme={null}
hostedUrl(input): string;
```

The URL of the checkout-v2 hosted checkout for a session.

`/checkout/{storeId}/{sessionId}` — the canonical, shareable, resumable
form. Both ids are in the path because the checkout must be able to
bootstrap itself from the URL alone (a link opened on another device has no
storage to fall back on).

**checkout-v2 only, and it asks the platform nothing.** A shop on the
legacy checkout needs a URL naming an order the platform has to create
first, which no synchronous local builder can produce — so for such a shop
this returns a link that cannot be opened. Prefer [start](#start-2), which asks
which checkout the shop runs and returns the right URL either way; reach
for this only when you hold a checkout-v2 session id and want the URL
without a round trip.

`storeId` is the shop's NUMERIC id, `Cart.storeId`. It is not the prefix
in the publishable key (`shopkit.shopPrefix`, `101928Y`), which the
checkout answers with "We couldn't find 101928Y". The id is taken from the
input when given, else from the one remembered off the last cart this
client received; when neither exists this throws rather than guess — pass
`storeId: cart.storeId`, or use [start](#start-2), which has the cart in hand.

###### Parameters

| Parameter | Type |
| - | - |
| `input` | `string` \| [`HostedCheckoutUrlInput`](#hostedcheckouturlinput) |

###### Returns

`string`

##### mount()

```ts theme={null}
mount(
   container, 
   input, 
options?): Promise<EmbeddedCheckout>;
```

Render the checkout INSIDE this page, in an iframe served from the
checkout's own origin.

```ts theme={null}
const checkout = await shopkit.checkout.mount("#checkout", {
  successUrl: "/thanks",
  backUrl: "/cart",
})
checkout.on("complete", ({ orderNumber }) => track(orderNumber))
```

One request, not two: this is [start](#start-2)'s call to `/checkout/handoff`
with an `embed` block added, so a mounted checkout costs a storefront
exactly what a redirected one does.

**It always resolves.** When there is nothing to frame — the shop runs the
legacy checkout, the platform returned no embed URL, or this page cannot
navigate its own top window — the handle comes back in its `fallback`
state, emits `fallback`, and (unless `fallbackRedirect: false`) sends the
shopper to the hosted checkout instead. None of those is a bug in the
calling page and none of them is retryable, so none of them throws. A
genuine failure — no cart, a rejected key, a 500 — still rejects, exactly
as `start()` would.

The same hosted URL is kept for one more case, decided later: a frame that
is built but never answers (this page is not the origin the session was
created for, typically). Fifteen seconds in, the handle takes the frame
down, moves to `fallback` with reason `handshake-timeout`, and sends the
shopper the same way — so a misconfigured `embedOrigin` costs a redirect,
not a sale.

**One checkout per container.** Mounting into an element that already
shows one destroys the earlier handle — its frame, its listeners, its
handshake timer — before the new frame goes in, so a page that calls
`mount()` again from a re-render or a "try again" button ends up with one
checkout, not two stacked. Last mount wins. The platform hands the same
cart the same session back, so the new frame shows the same checkout at
the same step; nothing is charged twice. `<qb-checkout>` lives by the same
rule when it is detached and put back. Keep the handle if you want to
`destroy()` it yourself; you do not have to for a second `mount()`.

Browser only: it builds a DOM node and listens for `message`.

###### Parameters

| Parameter | Type |
| - | - |
| `container` | `string` \| `Element` |
| `input` | [`MountCheckoutInput`](#mountcheckoutinput) |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`EmbeddedCheckout`](#embeddedcheckout)>

##### parseReturnUrl()

```ts theme={null}
parseReturnUrl(url): HostedCheckoutReturn | null;
```

Parse the hosted checkout's post-payment redirect.

The shopper is sent to `<successUrl origin>/success/<orderNumber>?hash=…&t=…`
— note that only the ORIGIN of `successUrl` is used, so this path is where a
headless storefront must mount its thank-you route. Both checkouts return
this way, the legacy one included (see `LegacyHandoff`); the handle to
confirm the order by is the one `start()` remembered either way.

`hash` is a legacy integrity token and is returned for completeness only;
treat the order as real once [confirmation](#confirmation) says `completed`.

###### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` \| `URL` |

###### Returns

[`HostedCheckoutReturn`](#hostedcheckoutreturn) | `null`

##### pollConfirmation()

```ts theme={null}
pollConfirmation(sessionId, options?): Promise<ConfirmationOutcome>;
```

Poll until the order exists, the payment terminally fails, or the caps are
reached. This is what a thank-you page should call.

A `timeout` outcome is NOT a failure — the order may still land. Show "we
are still processing your order" and let the customer refresh. On a legacy
handoff `timeout` is the NORMAL outcome for a shopper who has not paid yet,
because that checkout never reports `failed`; do not present it as an error
there.

###### Parameters

| Parameter | Type |
| - | - |
| `sessionId` | `string` |
| `options?` | [`PollConfirmationOptions`](#pollconfirmationoptions) |

###### Returns

`Promise`\<[`ConfirmationOutcome`](#confirmationoutcome)>

##### rememberedStoreId()

```ts theme={null}
rememberedStoreId(): string | null;
```

The shop's numeric store id as remembered from the last checkout session
or cart this client saw, or null when nothing is known yet — the value
the return leg needs after the cart is gone (a purchase event's dedup
key, `hostedUrl()` without a cart). Synchronous and request-free, like
`hostedUrl()`; see `SessionStore.peekStoreId` for what "remembered"
covers. The session's copy is consulted first because that is the one
still alive on a thank-you page.

###### Returns

`string` | `null`

##### resume()

```ts theme={null}
resume(
   container, 
   sessionId, 
input?): Promise<EmbeddedCheckout>;
```

Frame an EXISTING session — the redirect break-out's landing.

A redirect payment method (Klarna, Swish, iDEAL, a full-page 3-D Secure)
takes the whole window to the PSP and comes back to the checkout's own
origin, which bounces to the page that created the session with
`?qb_checkout_session=<id>` on it. This is what that page calls with the
id: no handoff, no new session, straight back into the frame the shopper
was already in, which resumes at its confirmation step.

`<qb-checkout>` and `<Checkout>` do this for you. Reach for it directly
only when you route that parameter yourself.

The URL is built locally, because there is no handoff call to ask. Pass
`storeId` — the return URL carries it as `qb_checkout_shop`, next to the
session id (`readReturnedCheckout()` reads both) — and no storage is
involved at all. Without it the id remembered with the session, then the
one off the last cart, is used; only when none of the three exists does
this reject.

Degrades the way [mount](#mount) does when the frame never answers: the
hosted checkout URL for the same session is built from the same inputs,
and a `handshake-timeout` sends the shopper there (unless
`fallbackRedirect: false`). A return leg that lands on the wrong origin —
a `www` redirect between leaving and coming back — thus still reaches the
confirmation, on the hosted checkout, instead of an empty box.

###### Parameters

| Parameter | Type |
| - | - |
| `container` | `string` \| `Element` |
| `sessionId` | `string` |
| `input?` | [`ResumeCheckoutInput`](#resumecheckoutinput) |

###### Returns

`Promise`\<[`EmbeddedCheckout`](#embeddedcheckout)>

##### start()

```ts theme={null}
start(input, options?): Promise<StartCheckoutResult>;
```

Cart → session → handoff URL, in one call. The 90%-case entry point for a
"Checkout" button.

Returns the URL rather than navigating, so it works unchanged in a server
action (`redirect(url)`), a route handler (302) and the browser
(`location.assign`). The URL's store id comes from the cart itself —
the only place the storefront API states the shop's numeric id.

On a server client (one built with a `cookies` accessor) the shopper's
consent decision is forwarded on the URL too, read from the request's
`qb_consent` cookie: `consentCategories`, and the GA client and session
ids when analytics is granted and the accessor has `getAll()`. That is
what lets the hosted checkout set Consent Mode before its own tags load.
`consent: false` in the config turns it off. In a browser the React hooks
and the elements decorate the URL from the live consent store instead.

###### Parameters

| Parameter | Type |
| - | - |
| `input` | [`StartCheckoutInput`](#startcheckoutinput) |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`StartCheckoutResult`](#startcheckoutresult)>

##### syncCart()

```ts theme={null}
syncCart(sessionId, options?): Promise<CheckoutSessionSnapshot | null>;
```

Re-read the cart into a session whose snapshot has gone stale.

Session creation is **idempotent on `cartId`** server-side: a second
`POST /checkout/sessions` for the same cart returns the FIRST session,
complete with the cart as it looked back then. That is right for a retry
and wrong for a shopper who went to checkout, came back, added something,
and set off again — their new lines would never reach the checkout, with no
error anywhere to say so.

Patching `cart: {}` is the platform's own "rehydrate from the Cart API"
instruction, and is what the hosted checkout does when the shopper edits
quantities inside it.

Only patches when the snapshot actually differs, so a session the shopper
is midway through is not needlessly disturbed. Pass `cart` when you already
have it (`start()` does) and the check costs no request at all.

###### Parameters

| Parameter | Type |
| - | - |
| `sessionId` | `string` |
| `options?` | [`RequestOptions`](#requestoptions) & `object` |

###### Returns

`Promise`\<[`CheckoutSessionSnapshot`](#checkoutsessionsnapshot) | `null`>

***

### ConsentResource

The shopper's consent decision as this client sees it, and the settings
every part of the kit has to agree on to read it.

Not a request to the platform: the decision lives in the `qb_consent`
cookie on the storefront's own origin, so on a server it is read through
the same `cookies` accessor the cart id is, and in a browser from
`document.cookie`.

```ts theme={null}
// A Next.js layout: no banner flash for a shopper who already decided.
const shopkit = await readOnlyShopkit()
const consent = await shopkit.consent.read()
<ShopkitProvider config={config} consent={{ initialState: consent }}>
```

#### Constructors

##### Constructor

```ts theme={null}
new ConsentResource(config): ConsentResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `config` | [`ResolvedShopkitConfig`](#resolvedshopkitconfig) |

###### Returns

[`ConsentResource`](#consentresource)

#### Accessors

##### cookieName

###### Get Signature

```ts theme={null}
get cookieName(): string;
```

The cookie the decision is stored in.

###### Returns

`string`

##### enabled

###### Get Signature

```ts theme={null}
get enabled(): boolean;
```

False when the config says `consent: false`.

###### Returns

`boolean`

##### revision

###### Get Signature

```ts theme={null}
get revision(): number;
```

The cookie-policy revision a stored decision must match.

###### Returns

`number`

#### Methods

##### decorateHandoff()

```ts theme={null}
decorateHandoff(url): Promise<string>;
```

A hosted checkout URL with the decision from this client's cookie
accessor on it (`consentCategories`, plus `gaClientId` / `gaSessionId`
when analytics is granted and the accessor has `getAll()`).

What `checkout.start()` applies on a server client. The URL comes back
untouched when consent is off or the client has no cookie accessor:
a browser client's handoff is decorated by the React hooks and the
elements instead, from the live consent store.

###### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` |

###### Returns

`Promise`\<`string`>

##### read()

```ts theme={null}
read(): Promise<ConsentState>;
```

The decision, from this client's cookies: the `cookies` accessor when
one was configured, else `document.cookie`, else undecided. A decision
made under another revision, or a cookie that is not one, reads as
undecided.

###### Returns

`Promise`\<[`ConsentState`](#consentstate-1)>

***

### ConsentStore

The shopper's consent decision, observable.

One per page, shared by the banner, the "Cookie settings" link in the
footer, every `<ConsentGate>` and the analytics hub — the same shape as
`CartStore` (`subscribe` / `getSnapshot`, an immutable snapshot replaced on
every change) so `useSyncExternalStore` and the elements' bindings can read
it the same way they read the cart.

The decision is the cookie's; this object is a view over it that can also
write. Reading happens once, at construction (or never, with
`initialState`), and every write goes through `save()` — there is no second
source of truth to drift from.

#### Constructors

##### Constructor

```ts theme={null}
new ConsentStore(options?): ConsentStore;
```

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`ConsentStoreOptions`](#consentstoreoptions) |

###### Returns

[`ConsentStore`](#consentstore)

#### Properties

##### getSnapshot()

```ts theme={null}
getSnapshot: () => ConsentSnapshot;
```

###### Returns

[`ConsentSnapshot`](#consentsnapshot)

##### revision

```ts theme={null}
readonly revision: number;
```

##### subscribe()

```ts theme={null}
subscribe: (listener) => () => void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | () => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

#### Accessors

##### pristine

###### Get Signature

```ts theme={null}
get pristine(): boolean;
```

True while the store still holds the state it started from: no decision
saved, reset or adopted since. A provider uses it to tell "nothing known
yet" from "the shopper (or something) has spoken" before applying a
state of its own.

###### Returns

`boolean`

#### Methods

##### acceptAll()

```ts theme={null}
acceptAll(): void;
```

###### Returns

`void`

##### allows()

```ts theme={null}
allows(category): boolean;
```

Whether a category may be used right now. `necessary` always may; the
others only after an explicit yes — undecided is a no.

###### Parameters

| Parameter | Type |
| - | - |
| `category` | [`ConsentCategory`](#consentcategory) |

###### Returns

`boolean`

##### closeSettings()

```ts theme={null}
closeSettings(): void;
```

###### Returns

`void`

##### getState()

```ts theme={null}
getState(): ConsentState;
```

The decision alone, without the UI flag — what gets persisted and forwarded.

###### Returns

[`ConsentState`](#consentstate-1)

##### hydrate()

```ts theme={null}
hydrate(state): void;
```

Adopt a state read elsewhere — the server's, or one re-read from a raw
cookie value. Does not persist and does not announce: this is catching
up with a decision, not making one.

###### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) \| `null` |

###### Returns

`void`

##### openSettings()

```ts theme={null}
openSettings(): void;
```

###### Returns

`void`

##### rejectAll()

```ts theme={null}
rejectAll(): void;
```

Necessary only. As prominent a choice as "accept all", by law.

###### Returns

`void`

##### reload()

```ts theme={null}
reload(): void;
```

Re-read `document.cookie`, for a page that knows another tab decided.

###### Returns

`void`

##### reset()

```ts theme={null}
reset(): void;
```

Withdraw consent: forget the decision and ask again. Withdrawal must be
as easy as consent, so this is one call — the banner reappears, Consent
Mode is updated to denied, and nothing optional fires until the next
decision. Scripts already on the page are not unloaded (nothing can
unload them); they simply receive nothing more.

###### Returns

`void`

##### save()

```ts theme={null}
save(choice): void;
```

Record a decision. Persists it (unless `persist: false`), closes the
preferences panel and announces it on `document` as `qb:consent`, so a
page's own scripts can react the way the hosted storefront's do.

###### Parameters

| Parameter | Type |
| - | - |
| `choice` | [`ConsentChoice`](#consentchoice) |

###### Returns

`void`

***

### EmbeddedCheckout

A checkout rendered inside the merchant's own page, and the conversation with
it.

The iframe document is always served from the checkout's own origin, so the
PSP integration, the same-origin API proxy and the wallet domain
registrations keep working exactly as they do on the redirect path. What
changes is who owns the window: the frame may not navigate anything, so every
exit — the back link, a PSP redirect, the completed order — arrives here as a
message and is performed by this host.

A handle is also returned when there is nothing to embed (see
[EmbeddedCheckoutFallbackReason](#embeddedcheckoutfallbackreason)). That is not an error and does not
throw: it is an expected shop configuration, reported as a `fallback` event
on a handle whose `state` is `"fallback"`. An embed handle whose frame never
answers ends up in the same place: the frame is removed, `state` flips to
`"fallback"`, and the same event carries the hosted checkout URL — so one
listener covers every way the shopper can end up on the hosted checkout.

#### Constructors

##### Constructor

```ts theme={null}
new EmbeddedCheckout(init): EmbeddedCheckout;
```

###### Parameters

| Parameter | Type |
| - | - |
| `init` | [`EmbeddedCheckoutInit`](#embeddedcheckoutinit) |

###### Returns

[`EmbeddedCheckout`](#embeddedcheckout)

#### Properties

##### sessionId

```ts theme={null}
readonly sessionId: string | null;
```

The session being checked out, when one is known.

#### Accessors

##### iframe

###### Get Signature

```ts theme={null}
get iframe(): HTMLIFrameElement | null;
```

The frame, or null in a fallback handle.

###### Returns

`HTMLIFrameElement` | `null`

##### state

###### Get Signature

```ts theme={null}
get state(): "fallback" | "embed";
```

`"embed"` while framed, `"fallback"` when there was nothing to frame — or
when the frame there was never answered and has been taken down.

###### Returns

`"fallback"` | `"embed"`

#### Methods

##### cartUpdated()

```ts theme={null}
cartUpdated(): void;
```

Tell the frame that the cart it is checking out has changed here.

The session's cart is a snapshot taken when the session was created, so a
page that lets the shopper edit the cart while the checkout is on screen —
its own quantity steppers, an "add a gift box" button, a cross-sell strip —
has to say so, or the frame goes on showing (and charging for) the cart as
it stood a moment ago. The frame re-reads the cart from the platform and
re-prices everything that hangs off it; nothing about the new cart travels
in this message.

`<qb-checkout>` and React's `<Checkout>` call this for you, off the kit's
own cart store. Call it yourself only when you drove `mount()` by hand, or
when you change the cart through something other than
`Quickbutik.cart` — and then only after the change has actually been
saved, because the frame reads the server's cart, not yours.

Held — not dropped — before the frame has said `ready`, and ignored by a
fallback handle: there is no frame to tell, and there never will be. It is
cheap and safe to over-call — the frame coalesces a burst into at most one
extra read.

Holding it is the whole point. The frame does NOT re-read the cart as it
boots: the session's cart is a snapshot taken when the session was
created, and this message is the only thing that refreshes it (see
`use-cart-rehydrate.ts` in checkout-frontend, and EMBED\_CONTRACT §3). The
window between the frame being built and its first `ready` is the iframe
load plus the frame's own session fetch — seconds on mobile — and a
mutation dropped in it would be a shopper charged for the cart as it
stood before they changed it. One flag, not a queue: the message carries
nothing, so two of them say exactly what one says.

###### Returns

`void`

##### destroy()

```ts theme={null}
destroy(): void;
```

Remove the frame and every listener.

Idempotent, and safe to call before the handshake completes — which is
what React's StrictMode double-mount does on every development render.

###### Returns

`void`

##### focus()

```ts theme={null}
focus(): void;
```

Ask the frame to focus its first control.

###### Returns

`void`

##### off()

```ts theme={null}
off<T>(type, handler): void;
```

###### Type Parameters

| Type Parameter |
| - |
| `T` *extends* keyof [`EmbeddedCheckoutEventMap`](#embeddedcheckouteventmap) |

###### Parameters

| Parameter | Type |
| - | - |
| `type` | `T` |
| `handler` | [`EmbeddedCheckoutHandler`](#embeddedcheckouthandler)\<`T`> |

###### Returns

`void`

##### on()

```ts theme={null}
on<T>(type, handler): () => void;
```

Subscribe. Returns an unsubscribe, so a one-liner needs no `off`.

###### Type Parameters

| Type Parameter |
| - |
| `T` *extends* keyof [`EmbeddedCheckoutEventMap`](#embeddedcheckouteventmap) |

###### Parameters

| Parameter | Type |
| - | - |
| `type` | `T` |
| `handler` | [`EmbeddedCheckoutHandler`](#embeddedcheckouthandler)\<`T`> |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

***

### ProductController

The variant-selection state machine for one product, with no framework
attached.

It owns nothing but a `VariantSelection` — every field on the snapshot is
derived from `(product, selection)` by the pure functions in
`variant-matrix.ts`. That is deliberate: the matrix is the only place variant
logic lives, and both bindings over it (React's `useProductState`, the
`<qb-product>` element) are glue.

Built for `useSyncExternalStore`, hence the cached snapshot: that hook
compares snapshots by identity and would loop forever on a freshly-allocated
object. The same caching is what lets the elements layer skip re-binding the
DOM when nothing changed.

#### Constructors

##### Constructor

```ts theme={null}
new ProductController(product, options?): ProductController;
```

###### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `options?` | [`ProductControllerOptions`](#productcontrolleroptions) |

###### Returns

[`ProductController`](#productcontroller)

#### Properties

##### getSnapshot()

```ts theme={null}
getSnapshot: () => ProductSnapshot;
```

###### Returns

[`ProductSnapshot`](#productsnapshot)

##### subscribe()

```ts theme={null}
subscribe: (listener) => () => void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | () => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

#### Accessors

##### product

###### Get Signature

```ts theme={null}
get product(): Product;
```

###### Returns

[`Product`](#product-1)

#### Methods

##### clear()

```ts theme={null}
clear(optionId): void;
```

Unset one group's choice.

###### Parameters

| Parameter | Type |
| - | - |
| `optionId` | `number` |

###### Returns

`void`

##### reset()

```ts theme={null}
reset(): void;
```

Unset every choice.

###### Returns

`void`

##### select()

```ts theme={null}
select(optionId, valueId): void;
```

Choose a value. Contradicting choices are cleared, not refused.

###### Parameters

| Parameter | Type |
| - | - |
| `optionId` | `number` |
| `valueId` | `number` |

###### Returns

`void`

##### selectVariant()

```ts theme={null}
selectVariant(variantId): void;
```

Jump straight to a variant, e.g. from a `?variant=` deep link.

###### Parameters

| Parameter | Type |
| - | - |
| `variantId` | `number` |

###### Returns

`void`

##### setProduct()

```ts theme={null}
setProduct(product, options?): void;
```

Point the controller at a different product, resetting the selection.

A quick-view that swaps items reuses one controller, and the previous
product's option ids are meaningless against the new one — keeping them
would resolve a variant that does not exist. Re-selecting the SAME product
is a no-op, so a re-render that happens to pass an equal object does not
throw away what the shopper picked.

###### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `options?` | [`ProductControllerOptions`](#productcontrolleroptions) |

###### Returns

`void`

***

### ProductsResource

The shop's public catalog.

Reads hit api-core's storefront controller, which serves only visible
products and omits everything a shopper has no business seeing (cost prices,
suppliers, internal notes). There is no write surface here on purpose: a
publishable key cannot create or edit products.

#### Constructors

##### Constructor

```ts theme={null}
new ProductsResource(
   transport, 
   scopes, 
   storefront?, 
   currency?): ProductsResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `transport` | [`Transport`](#transport) |
| `scopes` | [`ScopeGuard`](#scopeguard) |
| `storefront?` | [`StorefrontBinding`](#storefrontbinding) \| `null` |
| `currency?` | `CurrencyPreference` \| `null` |

###### Returns

[`ProductsResource`](#productsresource)

#### Methods

##### get()

```ts theme={null}
get(productId, options?): Promise<Product | null>;
```

A single product by its prefixed (`prod_123`) or numeric id.
Returns null when it does not exist or is not visible.

A bare number is prefixed on the way out — the gateway validates the path
param against `^prod_\d+$` and 400s a plain `27`. See
normalizePrefixedId.

###### Parameters

| Parameter | Type |
| - | - |
| `productId` | `string` \| `number` |
| `options?` | [`RequestOptions`](#requestoptions) & [`ProductReadOptions`](#productreadoptions) |

###### Returns

`Promise`\<[`Product`](#product-1) | `null`>

##### getBySlug()

```ts theme={null}
getBySlug(slug, params?): Promise<Product | null>;
```

Find a product by its URL slug.

**This walks pages until it matches.** The storefront product list accepts
only `limit`/`offset` — there is no `?slug=` filter to push the lookup
server-side — so the cost grows with the catalog. That is fine for a small
shop and for build-time generation, and wrong for a hot request path on a
large one. Two ways to avoid it:

* cache the result per slug (in Next.js, wrap the call in `cache()` and set
  a revalidate window), or
* build a slug → id map once at deploy time from [listAll](#listall) and look
  the id up with [get](#get-4), which is a single request.

Matching is case-insensitive and ignores surrounding slashes, so a slug
taken straight from a route param works. Returns null when nothing matches.

Every page it walks is priced for the same campaign — the client's, or
`storefrontId` — so the product it returns carries that campaign's prices.

###### Parameters

| Parameter | Type |
| - | - |
| `slug` | `string` |
| `params?` | `object` & [`RequestOptions`](#requestoptions) & [`ProductReadOptions`](#productreadoptions) |

###### Returns

`Promise`\<[`Product`](#product-1) | `null`>

##### list()

```ts theme={null}
list(params?): Promise<Page<Product>>;
```

One page of products, newest configured order first.

###### Parameters

| Parameter | Type |
| - | - |
| `params?` | [`ProductListParams`](#productlistparams) |

###### Returns

`Promise`\<[`Page`](#page)\<[`Product`](#product-1)>>

##### listAll()

```ts theme={null}
listAll(params?): Promise<Product[]>;
```

Walk every page. Convenience for build-time generation (static params,
sitemaps) — not something to call while rendering a request.

###### Parameters

| Parameter | Type |
| - | - |
| `params?` | `object` & [`RequestOptions`](#requestoptions) & [`ProductReadOptions`](#productreadoptions) |

###### Returns

`Promise`\<[`Product`](#product-1)\[]>

##### search()

```ts theme={null}
search(params?): Promise<Page<Product>>;
```

Filter, sort and page the catalog **server-side**.

This is the method to reach for when building a real listing. `list()`
offers nothing but `limit`/`cursor`, which forces a storefront to read the
whole catalog and filter in its own process — fine for a handful of
products, wrong the moment there are thousands.

```ts theme={null}
const page = await shopkit.products.search({
  search: "merino",
  sortBy: "price",
  sortOrder: "asc",
  minPrice: 20000,      // minor units — 200,00 kr
  categoryId: "cat_12",
  limit: 24,
})
page.data          // Product[]
page.next_cursor   // feed back as `cursor`
```

Two things it deliberately does not do:

* **No facet counts.** The gateway rebuilds every paginated response into
  `{ data, has_more, next_cursor }`, so an aggregate could not survive the
  trip even if the server sent one. Derive facet values from
  `categories.list()`, or keep your own index.
* **No option-value filter.** Option values are per-product rows, so "Svart"
  is a different id on every product and a cross-catalog filter would have
  to match on an unindexed name column.

Only visible products are ever returned, and — unlike `list()` — visibility
is applied in the query rather than after paging, so pages come back full.

###### Parameters

| Parameter | Type |
| - | - |
| `params?` | [`ProductSearchParams`](#productsearchparams) |

###### Returns

`Promise`\<[`Page`](#page)\<[`Product`](#product-1)>>

***

### SessionStore

What shopkit remembers between requests, and nothing else: the cart id, the
checkout session id, and the numeric id of the shop — twice.

Deliberately minimal: all of them are opaque server-side handles, so a leaked
or stale value costs at most a fresh cart. No prices, no PII and no
credential is ever persisted — that is what keeps a cookie-based store safe
to use without consent banners in most jurisdictions (it is strictly
necessary functionality) and what makes "just clear it" a valid recovery.

The store id is the odd one out. shopkit never needs it to talk to the API
(the publishable key already scopes every request to one shop), but it is
the one value the hosted checkout URL is built from, and the API only ever
states it on a cart. Remembering it off every cart that passes through is
what lets `checkout.hostedUrl()` stay a synchronous, request-free call.

It is kept twice because the two copies are written at different times and
expire on different clocks. The copy next to the cart id is refreshed off
every cart and expires with the cart's TTL; the copy next to the checkout
session id is written on handoff and goes with the session
(`clearCheckoutSessionId`), so the return leg of an embedded checkout can
build `/embed/{storeId}/{sessionId}` from a value no cart read disturbs.
Neither copy is removed when the cart is FORGOTTEN: the platform drops the
cart the moment an order is created, which on a redirect payment (Klarna,
Swish) is normally before the shopper is back on the page, and the
thank-you page then keys its `purchase` event on the id
(`checkout.rememberedStoreId()`). A reload of that page, after a header
badge has found the cart gone and the completed confirmation has forgotten
the session, must still find the same value or the order is counted twice.
Only `clear()` forgets it.

#### Constructors

##### Constructor

```ts theme={null}
new SessionStore(
   storage, 
   prefix, 
   ttlSeconds): SessionStore;
```

###### Parameters

| Parameter | Type |
| - | - |
| `storage` | [`StorageAdapter`](#storageadapter) |
| `prefix` | `string` |
| `ttlSeconds` | `number` |

###### Returns

[`SessionStore`](#sessionstore)

#### Properties

##### storage

```ts theme={null}
readonly storage: StorageAdapter;
```

#### Methods

##### clear()

```ts theme={null}
clear(): Promise<void>;
```

Forget everything: the cart, the session and both copies of the store id.

###### Returns

`Promise`\<`void`>

##### clearCartId()

```ts theme={null}
clearCartId(): Promise<void>;
```

###### Returns

`Promise`\<`void`>

##### clearCheckoutSessionId()

```ts theme={null}
clearCheckoutSessionId(): Promise<void>;
```

Forget the checkout session, and the store id remembered with it.

###### Returns

`Promise`\<`void`>

##### clearStoreId()

```ts theme={null}
clearStoreId(): Promise<void>;
```

Forget the cart's copy of the store id. The checkout's copy stays. Only
`clear()` calls this: forgetting a cart keeps the id (see the class doc).

###### Returns

`Promise`\<`void`>

##### getCartId()

```ts theme={null}
getCartId(): Promise<string | null>;
```

###### Returns

`Promise`\<`string` | `null`>

##### getCheckoutSessionId()

```ts theme={null}
getCheckoutSessionId(): Promise<string | null>;
```

###### Returns

`Promise`\<`string` | `null`>

##### getStoreId()

```ts theme={null}
getStoreId(): Promise<string | null>;
```

The remembered numeric shop id, or null. Also primes [peekStoreId](#peekstoreid).

###### Returns

`Promise`\<`string` | `null`>

##### peekCheckoutStoreId()

```ts theme={null}
peekCheckoutStoreId(): string | null;
```

The checkout session's store id without waiting on storage — the second
fallback of `hostedUrl()` and `embedUrl()`, after the cart's copy. Same
rules as [peekStoreId](#peekstoreid).

###### Returns

`string` | `null`

##### peekStoreId()

```ts theme={null}
peekStoreId(): string | null;
```

The remembered store id WITHOUT waiting on storage — what the synchronous
`hostedUrl()` reads.

Answers from memory first (every cart the client received set it), then
from storage when the adapter answers synchronously — memory,
`localStorage`, `document.cookie` and a request cookie jar all do, which
is what carries the value across page loads and server requests. An
adapter that only answers with a promise (Next's `cookies()`) cannot be
consulted here; until a cart has been read in that request the answer is
null and the caller has to be told the id explicitly.

###### Returns

`string` | `null`

##### setCartId()

```ts theme={null}
setCartId(cartId): Promise<void>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `cartId` | `string` |

###### Returns

`Promise`\<`void`>

##### setCheckoutSessionId()

```ts theme={null}
setCheckoutSessionId(sessionId, storeId?): Promise<void>;
```

Checkout sessions are short-lived by nature (one purchase attempt), so they
get a much shorter TTL than the cart — a day is generous for a shopper who
wanders off mid-payment and comes back.

The stored value is the HANDOFF handle, which is a checkout-v2 session id
for a shop on the current checkout and a legacy order uuid for a shop on
the old one (see `StartCheckoutResult.handoffId`). Both are v4 UUIDs and
both are what `checkout.confirmation()` takes, so nothing downstream has to
tell them apart — but do not assume a stored value addresses a session.

`storeId` is the shop's numeric id the session's URLs are built from,
remembered alongside for as long as the session is (see the class doc).
Best-effort, like [setStoreId](#setstoreid): the session id is the value nothing
else can recover, so that write is the one allowed to fail loudly.

###### Parameters

| Parameter | Type |
| - | - |
| `sessionId` | `string` |
| `storeId?` | `string` \| `number` \| `null` |

###### Returns

`Promise`\<`void`>

##### setStoreId()

```ts theme={null}
setStoreId(storeId): Promise<void>;
```

Remember the shop's numeric id, noted off every cart. Written with the
cart's TTL, and kept when the cart is forgotten (see the class doc).

Best-effort on the storage side. This runs on every cart READ, and a cart
page must not fail because its cookie jar is read-only (an RSC) or full.
The in-memory copy is kept regardless, so `hostedUrl()` works for the rest
of the request either way. Storage is only written when the value is new,
so a server request that merely reads the cart does not emit a cookie.

###### Parameters

| Parameter | Type |
| - | - |
| `storeId` | `string` \| `number` |

###### Returns

`Promise`\<`void`>

***

### ShopkitApiError

A non-2xx response from the Quickbutik API.

#### Extends

* [`ShopkitError`](#shopkiterror)

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitApiError(
   message, 
   status, 
   body?, 
   details?): ShopkitApiError;
```

###### Parameters

| Parameter | Type | Description |
| - | - | - |
| `message` | `string` | - |
| `status` | `number` | - |
| `body?` | `unknown` | Parsed response body when it was JSON, else the raw text, else null. |
| `details?` | `unknown` | `details` / `context` from the platform's error envelope, when present. |

###### Returns

[`ShopkitApiError`](#shopkitapierror)

###### Overrides

[`ShopkitError`](#shopkiterror).[`constructor`](#constructor-14)

#### Properties

##### body

```ts theme={null}
readonly body: unknown;
```

Parsed response body when it was JSON, else the raw text, else null.

##### details

```ts theme={null}
readonly details: unknown;
```

`details` / `context` from the platform's error envelope, when present.

##### status

```ts theme={null}
readonly status: number;
```

#### Accessors

##### retryable

###### Get Signature

```ts theme={null}
get retryable(): boolean;
```

True for the statuses where retrying the same request can plausibly help.

###### Returns

`boolean`

#### Methods

##### fromResponse()

```ts theme={null}
static fromResponse(response, fallbackMessage): Promise<ShopkitApiError>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `response` | `Response` |
| `fallbackMessage` | `string` |

###### Returns

`Promise`\<[`ShopkitApiError`](#shopkitapierror)>

***

### ShopkitClient

A configured Quickbutik storefront client.

One instance is cheap and stateless apart from the remembered ids (cart,
checkout session, the cart's store id), so create one per request on the
server (the cookie accessor belongs to that request) and one per app in the
browser.

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitClient(config): ShopkitClient;
```

###### Parameters

| Parameter | Type |
| - | - |
| `config` | [`ShopkitConfig`](#shopkitconfig) |

###### Returns

[`ShopkitClient`](#shopkitclient)

#### Properties

##### cart

```ts theme={null}
readonly cart: CartResource;
```

##### categories

```ts theme={null}
readonly categories: CategoriesResource;
```

##### checkout

```ts theme={null}
readonly checkout: CheckoutResource;
```

##### consent

```ts theme={null}
readonly consent: ConsentResource;
```

The shopper's cookie-consent decision, as this client's cookies carry
it, and the settings (`revision`, `cookieName`) from the config. On a
server: `await shopkit.consent.read()`, handed to `<ShopkitProvider>` as
`consent={{ initialState }}` so the banner is right on the first paint.

##### products

```ts theme={null}
readonly products: ProductsResource;
```

##### runtime

```ts theme={null}
readonly runtime: ShopkitRuntimeInfo;
```

##### scopes

```ts theme={null}
readonly scopes: ScopeGuard;
```

##### shop

```ts theme={null}
readonly shop: ShopResource;
```

##### shopPrefix

```ts theme={null}
readonly shopPrefix: string;
```

The shop's storage prefix (`101928Y`), decoded from the publishable key.

It is the folder the shop's files live under on the CDN —
`https://cdn.quickbutik.com/images/<shopPrefix>/products/<file>` — and
the value to build an `imageBaseUrl` from. It is NOT the shop's numeric
id: the hosted checkout URL wants that one, and it is `Cart.storeId`
(`checkout.start()` reads it from the cart for you).

##### storefront

```ts theme={null}
readonly storefront: StorefrontBinding | null;
```

The campaign storefront this client sells into, with the surface
defaulted — or null for an ordinary shop client. Set via
`ShopkitConfig.storefront`.

#### Accessors

##### apiUrl

###### Get Signature

```ts theme={null}
get apiUrl(): string;
```

The commerce API origin this client talks to.

###### Returns

`string`

##### checkoutUrl

###### Get Signature

```ts theme={null}
get checkoutUrl(): string;
```

The hosted checkout origin shoppers are handed off to.

###### Returns

`string`

##### currency

###### Get Signature

```ts theme={null}
get currency(): string | null;
```

The currency this client browses in — the shopper's remembered choice,
else `ShopkitConfig.currency`, else null (the shop's own currency).

It is what is REQUESTED, not necessarily what comes back: a currency the
shop does not offer falls back to the shop's, so format prices with
`product.currency` / `cart.currency`, and use `describeCurrency()` with
`shop.get()` to know the mode.

Synchronous: answered from memory, or from storage when the adapter can
answer synchronously (memory, `localStorage`, cookies, a request cookie
jar). With an async-only adapter (Next's `cookies()`) this reads the
configured default; requests still consult the adapter.

###### Returns

`string` | `null`

##### defaultCurrency

###### Get Signature

```ts theme={null}
get defaultCurrency(): string | null;
```

The `currency` this client was configured with, ignoring any choice.

###### Returns

`string` | `null`

##### imageBaseUrl

###### Get Signature

```ts theme={null}
get imageBaseUrl(): string | null;
```

Fallback image base, or null when images are rendered from the absolute
`url` the API sends (the normal case). See `ShopkitConfig.imageBaseUrl`.

###### Returns

`string` | `null`

##### shopId

###### Get Signature

```ts theme={null}
get shopId(): string;
```

###### Deprecated

Renamed to [shopPrefix](#shopprefix) in 1.0.0-beta.4, because that is
what the value is: the shop's storage prefix from the key. It is NOT the
numeric id the hosted checkout needs — for that use `cart.storeId`, or let
`checkout.start()` read it from the cart. Removed before 1.0.0.

###### Returns

`string`

##### storage

###### Get Signature

```ts theme={null}
get storage(): StorageAdapter;
```

The underlying storage adapter, for tests and for advanced integrations.

###### Returns

[`StorageAdapter`](#storageadapter)

#### Methods

##### onCurrencyChange()

```ts theme={null}
onCurrencyChange(listener): () => void;
```

Be told when [currency](#currency) changes through `setCurrency()`. Returns an
unsubscribe function. The React hooks and the custom elements use this to
refetch prices and refresh the cart.

###### Parameters

| Parameter | Type |
| - | - |
| `listener` | (`currency`) => `void` |

###### Returns

```ts theme={null}
(): void;
```

###### Returns

`void`

##### setCurrency()

```ts theme={null}
setCurrency(currency): Promise<void>;
```

Browse in another currency: remember it, and notify
[onCurrencyChange](#oncurrencychange) subscribers so a UI can refetch.

```ts theme={null}
await shopkit.setCurrency("EUR")   // every read and checkout now asks for EUR
await shopkit.setCurrency(null)    // back to the default
```

The cart is unchanged — it is priced per request, so the same cart reads
in the new currency next time. Throws a `ShopkitConfigError` for a value
that is not a three-letter code; a code the shop does not offer is
accepted and simply resolves to the shop's currency server-side.

###### Parameters

| Parameter | Type |
| - | - |
| `currency` | `string` \| `null` |

###### Returns

`Promise`\<`void`>

##### withStorage()

```ts theme={null}
withStorage(storage): ShopkitClient;
```

A copy of this client bound to different storage — the idiomatic way to
reuse one long-lived configuration across many server requests, each with
its own cookie jar.

```ts theme={null}
const base = { publishableKey, apiUrl }
export function shopkitForRequest(request: Request) {
  return createShopkitClient(base).withStorage(
    createRequestCookieStorage(request),
  )
}
```

###### Parameters

| Parameter | Type |
| - | - |
| `storage` | [`StorageAdapter`](#storageadapter) |

###### Returns

[`ShopkitClient`](#shopkitclient)

***

### ShopkitConfigError

A bad `createShopkitClient` config — a malformed key, a missing apiUrl.

#### Extends

* [`ShopkitError`](#shopkiterror)

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitConfigError(message): ShopkitConfigError;
```

###### Parameters

| Parameter | Type |
| - | - |
| `message` | `string` |

###### Returns

[`ShopkitConfigError`](#shopkitconfigerror)

###### Overrides

[`ShopkitError`](#shopkiterror).[`constructor`](#constructor-14)

***

### ShopkitError

Base class for everything shopkit throws, so `catch` can narrow on one type.

#### Extends

* `Error`

#### Extended by

* [`ShopkitApiError`](#shopkitapierror)
* [`ShopkitConfigError`](#shopkitconfigerror)
* [`ShopkitNetworkError`](#shopkitnetworkerror)
* [`ShopkitScopeError`](#shopkitscopeerror)

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitError(message): ShopkitError;
```

###### Parameters

| Parameter | Type |
| - | - |
| `message` | `string` |

###### Returns

[`ShopkitError`](#shopkiterror)

###### Overrides

```ts theme={null}
Error.constructor
```

***

### ShopkitNetworkError

The request never produced a response (offline, DNS, abort, timeout).

#### Extends

* [`ShopkitError`](#shopkiterror)

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitNetworkError(message, cause): ShopkitNetworkError;
```

###### Parameters

| Parameter | Type |
| - | - |
| `message` | `string` |
| `cause` | `unknown` |

###### Returns

[`ShopkitNetworkError`](#shopkitnetworkerror)

###### Overrides

[`ShopkitError`](#shopkiterror).[`constructor`](#constructor-14)

#### Properties

##### cause

```ts theme={null}
readonly cause: unknown;
```

###### Overrides

```ts theme={null}
ShopkitError.cause
```

***

### ShopkitScopeError

A call was made that the client's declared scopes do not cover. Thrown
locally, BEFORE the request goes out, so the developer sees the missing scope
by name instead of an opaque 403 from the gateway.

#### Extends

* [`ShopkitError`](#shopkiterror)

#### Constructors

##### Constructor

```ts theme={null}
new ShopkitScopeError(
   required, 
   declared, 
   operation): ShopkitScopeError;
```

###### Parameters

| Parameter | Type |
| - | - |
| `required` | `string` |
| `declared` | readonly `string`\[] |
| `operation` | `string` |

###### Returns

[`ShopkitScopeError`](#shopkitscopeerror)

###### Overrides

[`ShopkitError`](#shopkiterror).[`constructor`](#constructor-14)

#### Properties

##### declared

```ts theme={null}
readonly declared: readonly string[];
```

##### required

```ts theme={null}
readonly required: string;
```

***

### ShopResource

Shop-level presentation data: name, logo, brand colour, default language,
terms link. Everything a storefront shell needs before it renders.

#### Constructors

##### Constructor

```ts theme={null}
new ShopResource(transport, scopes): ShopResource;
```

###### Parameters

| Parameter | Type |
| - | - |
| `transport` | [`Transport`](#transport) |
| `scopes` | [`ScopeGuard`](#scopeguard) |

###### Returns

[`ShopResource`](#shopresource)

#### Methods

##### get()

```ts theme={null}
get(options?): Promise<Shop>;
```

Served by the checkout's shop endpoint — the only shop surface a
publishable key can reach, hence the `checkout:read` scope for what looks
like plain branding.

###### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`RequestOptions`](#requestoptions) |

###### Returns

`Promise`\<[`Shop`](#shop-2)>

***

### Transport

The one place a request to Quickbutik is made.

A thin adapter over `@quickbutik/sdk-gateway`, the platform's own API client,
which is *bundled into* this package rather than depended on — it is
published to a private registry, so a third-party storefront could not
install it. Everything underneath comes from there: bearer auth derived from
the publishable key, the `QB-Version` header, per-attempt timeouts,
exponential-backoff retries that honour `Retry-After`, adaptive rate-limit
pacing driven by the gateway's `X-RateLimit-*` headers, and an automatic
`Idempotency-Key` on every mutation (so a retried `POST /cart` can never
create two carts). shopkit behaves like every other Quickbutik client
instead of re-implementing all of that.

What stays here is shopkit's own contract: mapping the platform's error
taxonomy onto shopkit's, treating a vanished resource as `null`, unwrapping
api-core's response envelope, and adopting a rotated publishable key.

#### Constructors

##### Constructor

```ts theme={null}
new Transport(config): Transport;
```

###### Parameters

| Parameter | Type |
| - | - |
| `config` | [`ResolvedShopkitConfig`](#resolvedshopkitconfig) |

###### Returns

[`Transport`](#transport)

#### Methods

##### request()

```ts theme={null}
request<T>(request): Promise<T>;
```

Send a request and return its body with any envelope removed.

###### Type Parameters

| Type Parameter |
| - |
| `T` |

###### Parameters

| Parameter | Type |
| - | - |
| `request` | [`TransportRequest`](#transportrequest-2) |

###### Returns

`Promise`\<`T`>

##### requestOrNull()

```ts theme={null}
requestOrNull<T>(request): Promise<T | null>;
```

Like [request](#requestornull-1) but resolves to null on 404/410 instead of throwing —
"not there" is a normal answer for a remembered id (a cart that expired, a
session that was cleaned up), not an error a storefront should crash on.

###### Type Parameters

| Type Parameter |
| - |
| `T` |

###### Parameters

| Parameter | Type |
| - | - |
| `request` | [`TransportRequest`](#transportrequest-2) |

###### Returns

`Promise`\<`T` | `null`>

##### requestVoid()

```ts theme={null}
requestVoid(request): Promise<void>;
```

Send a request whose response body is discarded (204 / plain OK).

###### Parameters

| Parameter | Type |
| - | - |
| `request` | [`TransportRequest`](#transportrequest-2) |

###### Returns

`Promise`\<`void`>

## Interfaces

### AddCartItemInput

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="productid" /> `productId` | `string` \| `number` | Either the prefixed id from `products.list()` (`"prod_123"`) or the raw numeric id. Both work — the gateway normalizes. |
| <a id="quantity" /> `quantity?` | `number` | Defaults to 1. |
| <a id="variantid" /> `variantId?` | `number` | Required for a product that has more than one variant. |

***

### AnalyticsContext

What the hub knows when it calls a destination.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="consent-1" /> `consent` | [`ConsentState`](#consentstate-1) \| `null` | The shopper's decision, or null when the hub runs without consent (`requireConsent: false`) — in which case every category is allowed. |
| <a id="currency-1" /> `currency?` | `string` | The default currency for events that carry none. |
| <a id="storeid" /> `storeId` | `string` \| `null` | The shop's numeric store id when known (the hub's `storeId` option, else learned from the attached cart), null until then. The purchase eventID is built from it. |

***

### AnalyticsDestination

Where events go. The kit ships Google (gtag.js / GTM) and the Meta Pixel;
anything else — TikTok, Klaviyo, a server-side collector — is one object
that implements this.

```ts theme={null}
const tiktok: AnalyticsDestination = {
  id: "tiktok",
  requires: "marketing",           // nothing happens before marketing consent
  load() { … inject the pixel … },
  track(event) { if (event.name === "view_item") ttq.track("ViewContent", …) },
}
```

#### Properties

| Property | Type | Description | |
| - | - | - | - |
| <a id="id" /> `id` | `string` | Stable, unique per hub. Used for the `[data-qb-analytics]` tag and in warnings. | |
| <a id="key" /> `key?` | `string` | Which vendor account this instance reports to, as a string: two destinations with the same `key` are the same destination. A provider whose `destinations` are an inline array (new objects on every render) keeps the one it has while the key stays, and swaps it when the key changes. The built-in destinations use their ids only (\`google:G-X | …`, `meta:123\`), so changing another option on the same ids needs a remount; without a key the object itself is the identity. |
| <a id="requires" /> `requires` | [`ConsentCategory`](#consentcategory) \| `null` | The consent category this destination needs before it receives anything — `load()` included. `null` means it is always loaded and always receives events, and is expected to gate itself (the Google destination does, through Consent Mode). | |

#### Methods

##### load()?

```ts theme={null}
optional load(context): void;
```

Put the vendor's script on the page. Called once, the first time the
destination is allowed, and only in a browser. Must be idempotent: a
second hub on the same page (two `<qb-shop>`s) calls it again.

###### Parameters

| Parameter | Type |
| - | - |
| `context` | [`AnalyticsContext`](#analyticscontext) |

###### Returns

`void`

##### onConsent()?

```ts theme={null}
optional onConsent(state, context): void;
```

The decision changed. Called after `load()`, on every change, allowed or not.

###### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) \| `null` |
| `context` | [`AnalyticsContext`](#analyticscontext) |

###### Returns

`void`

##### track()

```ts theme={null}
track(event, context): void;
```

###### Parameters

| Parameter | Type |
| - | - |
| `event` | [`CommerceEvent`](#commerceevent) |
| `context` | [`AnalyticsContext`](#analyticscontext) |

###### Returns

`void`

##### trackPurchase()?

```ts theme={null}
optional trackPurchase(
   purchase, 
   meta, 
   context): void;
```

The purchase, with the id Meta and the platform dedupe it by
(`purchase_{storeId}_{orderNumber}`). Optional: a destination without it
receives the purchase through `track()` as a `purchase` event instead.

###### Parameters

| Parameter | Type |
| - | - |
| `purchase` | [`PurchaseEvent`](#purchaseevent) |
| `meta` | \{ `eventId`: `string`; } |
| `meta.eventId` | `string` |
| `context` | [`AnalyticsContext`](#analyticscontext) |

###### Returns

`void`

***

### AnalyticsOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="buffersize" /> `bufferSize?` | `number` | How many events are remembered for destinations that cannot receive them yet (consent pending, vendor config still loading). Default 50, never fewer than 1: delivery itself reads from this memory, so a hub that remembered nothing would deliver nothing. |
| <a id="consent-2" /> `consent?` | [`ConsentStore`](#consentstore) \| `null` | The consent decision every destination is gated on. Required unless `requireConsent` is false: without a store and without that opt-out the hub treats the shopper as undecided forever (nothing optional is sent), with one warning in the console. |
| <a id="currency-2" /> `currency?` | `string` | The currency for events that carry none. Falls back to the cart's. |
| <a id="debug" /> `debug?` | `boolean` | Log every dispatch to the console. |
| <a id="destinations-1" /> `destinations?` | [`AnalyticsDestination`](#analyticsdestination)\[] | Where events go. More can be added later with `addDestination`. |
| <a id="requireconsent-1" /> `requireConsent?` | `boolean` | False runs every destination ungated — the hosted storefront's behaviour for a shop without the consent app. For a storefront that is certain it owes its shoppers no consent. Default true. |
| <a id="storeid-1" /> `storeId?` | `string` \| `number` \| `null` | The shop's numeric store id, for the purchase dedup key and Meta eventID. Falls back to the id on the attached cart (`Cart.storeId`), which is how the React provider and the elements learn it. |

***

### ApiEnvelope

api-core wraps most responses in this envelope. The gateway strips it on some
routes and forwards it on others, so the transport unwraps defensively rather
than per-route (see `unwrapEnvelope`).

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Properties

| Property | Type |
| - | - |
| <a id="data" /> `data?` | `T` |
| <a id="error" /> `error?` | `string` |
| <a id="message" /> `message?` | `string` |
| <a id="statuscode" /> `statusCode?` | `number` |

***

### AsyncSnapshot

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="data-1" /> `data` | `T` \| `null` | - |
| <a id="error-1" /> `error` | `Error` \| `null` | - |
| <a id="loading" /> `loading` | `boolean` | True while a request is in flight, including a refetch over stale data. |
| <a id="status-1" /> `status` | [`AsyncStatus`](#asyncstatus) | - |

***

### Cart

A server-owned cart. Prices are recomputed on every read, so a cart held in
a cookie for a week still reflects today's prices — never cache these
numbers client-side beyond the current render.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="createdat" /> `createdAt` | `string` | - |
| <a id="currency-3" /> `currency` | `string` | The currency every amount in THIS response is in. A cart stores no currency of its own: it is priced per request, in the client's chosen currency when the shop offers it, so the same cart reads in SEK on one request and EUR on the next. |
| <a id="id-1" /> `id` | `string` | - |
| <a id="itemcount" /> `itemCount` | `number` | Sum of quantities, not of lines. |
| <a id="items" /> `items` | [`CartItem`](#cartitem)\[] | - |
| <a id="presentment" /> `presentment?` | [`CartPresentment`](#cartpresentment-1) | How this response's currency relates to the checkout. Absent from a platform older than the field — read it as `mode: "base"`. |
| <a id="storefrontid" /> `storefrontId?` | `string` \| `null` | The campaign storefront this cart is bound to, or null for an ordinary cart. A bound cart is priced with the campaign's overrides on every read, and a checkout session created from it inherits the binding. Absent from a platform older than the field — read it as null. |
| <a id="storeid-2" /> `storeId` | `number` | - |
| <a id="subtotal" /> `subtotal` | `number` | - |
| <a id="subtotalexcltax" /> `subtotalExclTax` | `number` | - |
| <a id="subtotalincltax" /> `subtotalInclTax` | `number` | - |
| <a id="surface" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` \| `null` | Where the campaign was presented, recorded with the binding. |
| <a id="total" /> `total` | `number` | - |
| <a id="totaldiscount" /> `totalDiscount` | `number` | - |
| <a id="totalexcltax" /> `totalExclTax` | `number` | - |
| <a id="totalincltax" /> `totalInclTax` | `number` | - |
| <a id="totaltax" /> `totalTax` | `number` | - |
| <a id="updatedat" /> `updatedAt` | `string` | - |

***

### CartItem

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="available" /> `available` | `boolean` | False when the product was hidden or deleted after being added. |
| <a id="compareatprice" /> `compareAtPrice` | `number` \| `null` | - |
| <a id="discountamount" /> `discountAmount` | `number` | - |
| <a id="id-2" /> `id` | `string` | Cart line id (uuid) — the handle for update/remove, NOT the product id. |
| <a id="imageurl" /> `imageUrl` | `string` \| `null` | - |
| <a id="linetotal" /> `lineTotal` | `number` | - |
| <a id="linetotalexcltax" /> `lineTotalExclTax` | `number` | - |
| <a id="linetotalincltax" /> `lineTotalInclTax` | `number` | - |
| <a id="linetotaltax" /> `lineTotalTax` | `number` | - |
| <a id="productid-1" /> `productId` | `number` | - |
| <a id="producttitle" /> `productTitle` | `string` \| `null` | - |
| <a id="quantity-1" /> `quantity` | `number` | - |
| <a id="sku" /> `sku` | `string` \| `null` | - |
| <a id="taxamount" /> `taxAmount` | `number` | - |
| <a id="taxrate" /> `taxRate` | `number` | - |
| <a id="unitprice" /> `unitPrice` | `number` | Display unit price — incl. or excl. tax depending on the shop's setting. |
| <a id="unitpriceexcltax" /> `unitPriceExclTax` | `number` | - |
| <a id="unitpriceincltax" /> `unitPriceInclTax` | `number` | - |
| <a id="variantid-1" /> `variantId?` | `number` | - |
| <a id="variantname" /> `variantName` | `string` \| `null` | "Red / XL", built from the variant's option values. Null for simple products. |

***

### CartLineDiff

#### Properties

| Property | Type |
| - | - |
| <a id="added" /> `added` | [`CommerceItem`](#commerceitem)\[] |
| <a id="currency-4" /> `currency?` | `string` |
| <a id="removed" /> `removed` | [`CommerceItem`](#commerceitem)\[] |

***

### CartMutationEvent

A successful change made THROUGH this store, with the cart before and
after — what an analytics layer diffs into `add_to_cart` /
`remove_from_cart`. Never emitted for `load`, `refresh` or `hydrate`
(nothing changed, something was read) nor for a failed mutation.

#### Properties

| Property | Type |
| - | - |
| <a id="after" /> `after` | [`Cart`](#cart-1) \| `null` |
| <a id="before" /> `before` | [`Cart`](#cart-1) \| `null` |
| <a id="type" /> `type` | `"add"` \| `"update"` \| `"remove"` \| `"clear"` \| `"buy-now"` |

***

### CartPresentment

See [Cart.presentment](#presentment).

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="basecurrency" /> `baseCurrency` | `string` | The shop's own currency. |
| <a id="chargecurrency" /> `chargeCurrency` | `string` | What the checkout will charge in. |
| <a id="exchangerate" /> `exchangeRate` | `number` | Units of `currency` per 1 unit of `baseCurrency`. 1 for `"base"`. |
| <a id="mode" /> `mode` | [`ShopCurrencyMode`](#shopcurrencymode-1) | - `"base"` — amounts are in the shop's own currency. - `"display"` — amounts are converted for display; the checkout charges `chargeCurrency` (the shop's currency). - `"charge"` — amounts are what the checkout will charge, in `currency`. |

***

### Category

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="ancestors" /> `ancestors` | [`CategoryAncestor`](#categoryancestor)\[] | Ancestors ordered root to immediate parent. Empty for a top-level category. |
| <a id="childcount" /> `childCount` | `number` | - |
| <a id="description" /> `description` | `string` \| `null` | - |
| <a id="description1" /> `description1` | `string` \| `null` | - |
| <a id="description2" /> `description2` | `string` \| `null` | - |
| <a id="id-3" /> `id` | `string` | Prefixed id (`cat_12`). |
| <a id="image" /> `image` | `string` \| `null` | The bare storage filename of the cover image. Not a URL — see [Category.imageUrl](#imageurl-1). |
| <a id="imageurl-1" /> `imageUrl` | `string` \| `null` | Absolute URL of the cover image, with no resize parameters. Null when the category has no image. Resolve it with `resolveCategoryImageUrl`. |
| <a id="name" /> `name` | `string` \| `null` | - |
| <a id="parentid" /> `parentId` | `string` \| `null` | - |
| <a id="path" /> `path` | `string` \| `null` | Full slug path, e.g. `clothing/shirts`. |
| <a id="seotitle" /> `seoTitle` | `string` \| `null` | - |
| <a id="slug" /> `slug` | `string` \| `null` | - |

***

### CategoryAncestor

One entry of [Category.ancestors](#ancestors): a lightweight parent reference.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="id-4" /> `id` | `string` | Prefixed id (`cat_2`). |
| <a id="name-1" /> `name` | `string` \| `null` | - |
| <a id="slug-1" /> `slug` | `string` \| `null` | The ancestor's own slug segment, not its full path. |

***

### CategoryListParams

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cursor" /> `cursor?` | `string` | - |
| <a id="limit" /> `limit?` | `number` | - |
| <a id="parentid-1" /> `parentId?` | `string` | - |
| <a id="root" /> `root?` | `boolean` | Only top-level categories. |
| <a id="search-2" /> `search?` | `string` | - |

***

### CheckoutCartProduct

A cart line as the checkout session snapshotted it.

#### Properties

| Property | Type |
| - | - |
| <a id="imageurl-2" /> `imageUrl` | `string` \| `null` |
| <a id="name-2" /> `name` | `string` |
| <a id="price" /> `price` | `number` |
| <a id="productid-2" /> `productId` | `string` |
| <a id="quantity-2" /> `quantity` | `number` |
| <a id="variantid-2" /> `variantId?` | `string` |
| <a id="variantname-1" /> `variantName?` | `string` \| `null` |
| <a id="weight" /> `weight?` | `number` \| `null` |

***

### CheckoutDataNodes

The data nodes shopkit types; the map carries others too.

#### Properties

| Property | Type |
| - | - |
| <a id="cart_products" /> `cart_products` | [`CheckoutCartProduct`](#checkoutcartproduct)\[] |
| <a id="discount" /> `discount` | [`OrderLine`](#orderline)\[] |
| <a id="order_total" /> `order_total` | [`CheckoutOrderTotal`](#checkoutordertotal) |
| <a id="payment_cost" /> `payment_cost` | [`OrderLine`](#orderline)\[] |
| <a id="pricing" /> `pricing` | [`CheckoutPricing`](#checkoutpricing) |
| <a id="promocode" /> `promocode` | [`OrderLine`](#orderline)\[] |
| <a id="shipping_cost" /> `shipping_cost` | [`OrderLine`](#orderline)\[] |

***

### CheckoutOrderTotal

The server-authoritative total: products + shipping + payment fee +
discounts. This is the figure the shopper is charged — never recompute it.

#### Properties

| Property | Type |
| - | - |
| <a id="currency-5" /> `currency` | `string` |
| <a id="lines" /> `lines` | [`OrderLine`](#orderline)\[] |
| <a id="subtotal-1" /> `subtotal` | `number` |
| <a id="subtotalexcltax-1" /> `subtotalExclTax` | `number` |
| <a id="subtotalincltax-1" /> `subtotalInclTax` | `number` |
| <a id="total-1" /> `total` | `number` |
| <a id="totaldiscount-1" /> `totalDiscount` | `number` |
| <a id="totalpaymentfee" /> `totalPaymentFee` | `number` |
| <a id="totalshipping" /> `totalShipping` | `number` |
| <a id="totaltax-1" /> `totalTax` | `number` |

***

### CheckoutPricing

Product-only subtotals. `order_total` is the one that includes everything.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="basecurrency-1" /> `baseCurrency?` | `string` | The shop's own currency, when the session is charged in another one. |
| <a id="currency-6" /> `currency` | `string` | The currency the session is priced AND charged in. |
| <a id="exchangerate-1" /> `exchangeRate?` | `number` | Units of `currency` per 1 unit of `baseCurrency`, when they differ. |
| <a id="subtotal-2" /> `subtotal` | `number` | - |
| <a id="subtotalexcltax-2" /> `subtotalExclTax` | `number` | - |
| <a id="subtotalincltax-2" /> `subtotalInclTax` | `number` | - |
| <a id="taxontop" /> `taxOnTop?` | `boolean` | Shop prices are ex-VAT and tax is added on top. Absent → false. |
| <a id="totaltax-2" /> `totalTax` | `number` | - |

***

### CheckoutSession

What `POST /v2/checkout/sessions` answers with.

#### Extended by

* [`V2Handoff`](#v2handoff)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="backurl" /> `backUrl` | `string` \| `null` | - |
| <a id="cancelurl" /> `cancelUrl` | `string` \| `null` | - |
| <a id="cartid-1" /> `cartId` | `string` | - |
| <a id="data-2" /> `data` | [`SessionDataMap`](#sessiondatamap) | - |
| <a id="displaycurrency" /> `displayCurrency?` | [`DisplayCurrency`](#displaycurrency-2) \| `null` | The currency the checkout shows an approximate amount in, or null when none applies (no currency sent, the shop's own currency, or a `"charge"` currency — then `data.pricing.value.currency` is it). Absent on older platforms. |
| <a id="embed" /> `embed?` | [`SessionEmbed`](#sessionembed) \| `null` | The embed permission recorded at creation, or null when there is none. |
| <a id="fields" /> `fields` | [`SessionFieldMap`](#sessionfieldmap) | - |
| <a id="language" /> `language` | `string` \| `null` | - |
| <a id="origin" /> `origin` | `string` \| `null` | `"storefront-native"` only for the built-in storefront; null for headless. |
| <a id="sessionid-1" /> `sessionId` | `string` | - |
| <a id="storefrontid-1" /> `storefrontId?` | `string` \| `null` | The campaign storefront the session is bound to, or null. Absent on older platforms. |
| <a id="successmode" /> `successMode?` | `"redirect"` \| `"inline"` | How this checkout ends, as the platform echoes it. Missing means `"redirect"`. |
| <a id="successurl" /> `successUrl` | `string` \| `null` | The success URL the session was created with, or null when the session was created in inline mode without a successUrl. |
| <a id="surface-1" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` \| `null` | - |
| <a id="theme" /> `theme` | `"light"` \| `"dark"` | The theme this checkout is painted in — always `"light"` or `"dark"`, never absent. This is the answer, not an echo: it is what you asked for when you asked for something, and the merchant's shop-wide choice when you did not. You cannot tell the two apart from here, and you do not need to — this is what the shopper sees. |

***

### CheckoutSessionSnapshot

What `GET /v2/checkout/sessions/:id` answers with.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="backurl-1" /> `backUrl` | `string` \| `null` | - |
| <a id="blockingfields" /> `blockingFields` | `string`\[] | Names of the fields still standing between the shopper and payment. |
| <a id="cancelurl-1" /> `cancelUrl` | `string` \| `null` | - |
| <a id="cartid-2" /> `cartId` | `string` \| `null` | - |
| <a id="data-3" /> `data` | [`SessionDataMap`](#sessiondatamap) | - |
| <a id="displaycurrency-1" /> `displayCurrency?` | [`DisplayCurrency`](#displaycurrency-2) \| `null` | See [CheckoutSession.displayCurrency](#displaycurrency). |
| <a id="embed-1" /> `embed?` | [`SessionEmbed`](#sessionembed) \| `null` | The embed permission recorded at creation, or null when there is none. |
| <a id="fields-1" /> `fields` | [`SessionFieldMap`](#sessionfieldmap) | - |
| <a id="iscomplete" /> `isComplete` | `boolean` | True when every required field is valid — the checkout can take payment. |
| <a id="language-1" /> `language` | `string` \| `null` | - |
| <a id="origin-1" /> `origin` | `string` \| `null` | - |
| <a id="storefrontid-2" /> `storefrontId?` | `string` \| `null` | The campaign storefront the session is bound to, or null. Absent on older platforms. |
| <a id="successmode-1" /> `successMode?` | `"redirect"` \| `"inline"` | How this checkout ends, as the platform echoes it. Missing means `"redirect"`. |
| <a id="successurl-1" /> `successUrl` | `string` \| `null` | The success URL the session was created with, or null when the session was created in inline mode without a successUrl. |
| <a id="surface-2" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` \| `null` | - |

***

### CommerceEvent

#### Properties

| Property | Type |
| - | - |
| <a id="name-3" /> `name` | [`CommerceEventName`](#commerceeventname-1) |
| <a id="params" /> `params` | [`CommerceEventParams`](#commerceeventparams-1) |

***

### CommerceEventParams

#### Indexable

```ts theme={null}
[key: string]: unknown
```

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="currency-7" /> `currency?` | `string` | - |
| <a id="items-1" /> `items?` | [`CommerceItem`](#commerceitem)\[] | - |
| <a id="value" /> `value?` | `number` | Major units. |

***

### CommerceItem

A GA4 item. `item_id` is the bare numeric PRODUCT id, never the variant's:
it is what the hosted storefront's tags emit and what the Google Shopping
feed carries, so it is the only id that matches a catalogue on the other
end. The variant rides along as `item_variant`. Prices are in MAJOR units.

#### Indexable

```ts theme={null}
[key: string]: unknown
```

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="item_category" /> `item_category?` | `string` | - |
| <a id="item_id" /> `item_id` | `string` | - |
| <a id="item_name" /> `item_name?` | `string` | - |
| <a id="item_variant" /> `item_variant?` | `string` | - |
| <a id="price-1" /> `price?` | `number` | Unit price in major units (`99`, not `9900`). |
| <a id="quantity-3" /> `quantity?` | `number` | - |

***

### ConsentChoice

What a shopper chooses: the optional categories, on or off.

#### Extended by

* [`ConsentState`](#consentstate-1)

#### Properties

| Property | Type |
| - | - |
| <a id="analytics-1" /> `analytics` | `boolean` |
| <a id="marketing" /> `marketing` | `boolean` |

***

### ConsentCookieOptions

#### Extended by

* [`ConsentCookieWriteOptions`](#consentcookiewriteoptions)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cookiename-1" /> `cookieName?` | `string` | Defaults to `qb_consent`. |
| <a id="revision-2" /> `revision?` | `number` | The revision a stored decision must match to count. Defaults to 1. |

***

### ConsentCookieWriteOptions

#### Extends

* [`ConsentCookieOptions`](#consentcookieoptions)

#### Extended by

* [`ConsentStoreOptions`](#consentstoreoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="cookiename-2" /> `cookieName?` | `string` | Defaults to `qb_consent`. | [`ConsentCookieOptions`](#consentcookieoptions).[`cookieName`](#cookiename-1) |
| <a id="domain" /> `domain?` | `string` | Share the decision across subdomains (`.myshop.com`). Host-only by default. | - |
| <a id="maxagedays" /> `maxAgeDays?` | `number` | Defaults to 180 days. | - |
| <a id="revision-3" /> `revision?` | `number` | The revision a stored decision must match to count. Defaults to 1. | [`ConsentCookieOptions`](#consentcookieoptions).[`revision`](#revision-2) |
| <a id="samesite" /> `sameSite?` | `"lax"` \| `"strict"` \| `"none"` | Defaults to `lax`. | - |
| <a id="secure" /> `secure?` | `boolean` | Defaults to true on https origins, false otherwise (localhost works). | - |

***

### ConsentLabels

The copy of the default banner, in the languages Quickbutik shops sell in.

The kit renders no markup of its own except where an empty element would be
a bug, and an empty consent banner is one — so `<ConsentBanner>` and
`<qb-consent-banner>` fill themselves with a minimal dialog when given no
content, and this is its text. Every string is overridable; a storefront
with its own voice passes `labels`.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="acceptall-2" /> `acceptAll` | `string` | - |
| <a id="analytics-2" /> `analytics` | `string` | - |
| <a id="analyticsdescription" /> `analyticsDescription` | `string` | - |
| <a id="close" /> `close` | `string` | - |
| <a id="cookiesettings" /> `cookieSettings` | `string` | The footer button that reopens the banner (`<ConsentSettingsButton>`). |
| <a id="description-1" /> `description` | `string` | - |
| <a id="marketing-1" /> `marketing` | `string` | - |
| <a id="marketingdescription" /> `marketingDescription` | `string` | - |
| <a id="necessary" /> `necessary` | `string` | - |
| <a id="necessarydescription" /> `necessaryDescription` | `string` | - |
| <a id="privacypolicy" /> `privacyPolicy` | `string` | - |
| <a id="rejectall-2" /> `rejectAll` | `string` | - |
| <a id="save-2" /> `save` | `string` | - |
| <a id="settings" /> `settings` | `string` | - |
| <a id="title" /> `title` | `string` | - |

***

### ConsentModeSignals

The four Google Consent Mode v2 signals the platform sets. The mapping is
the hosted storefront's, byte for byte: `analytics` drives
`analytics_storage`; `marketing` drives the three advertising signals.
`functionality_storage` and `personalization_storage` are deliberately not
set — the platform never has, and a storefront that wants them can push
its own `gtag('consent', …)` call.

#### Properties

| Property | Type |
| - | - |
| <a id="ad_personalization" /> `ad_personalization` | [`ConsentModeValue`](#consentmodevalue) |
| <a id="ad_storage" /> `ad_storage` | [`ConsentModeValue`](#consentmodevalue) |
| <a id="ad_user_data" /> `ad_user_data` | [`ConsentModeValue`](#consentmodevalue) |
| <a id="analytics_storage" /> `analytics_storage` | [`ConsentModeValue`](#consentmodevalue) |

***

### ConsentSnapshot

What the store exposes: the decision plus the one piece of UI state a banner
and a far-away "Cookie settings" link have to share — whether the
preferences panel is open. Not persisted.

#### Extends

* [`ConsentState`](#consentstate-1)

#### Extended by

* [`ConsentBannerState`](/kit/api/react#consentbannerstate)
* [`UseConsentResult`](/kit/api/react#useconsentresult)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="analytics-3" /> `analytics` | `boolean` | - | [`ConsentState`](#consentstate-1).[`analytics`](#analytics-4) |
| <a id="decidedat" /> `decidedAt` | `string` \| `null` | ISO 8601, when the shopper decided. Null while undecided. | [`ConsentState`](#consentstate-1).[`decidedAt`](#decidedat-1) |
| <a id="marketing-2" /> `marketing` | `boolean` | - | [`ConsentState`](#consentstate-1).[`marketing`](#marketing-3) |
| <a id="open" /> `open` | `boolean` | - | - |
| <a id="revision-4" /> `revision` | `number` | The policy revision the decision was made against. Bump the configured revision when the cookie policy changes materially and every shopper is asked again; a stored decision for an older revision reads as undecided. | [`ConsentState`](#consentstate-1).[`revision`](#revision-5) |
| <a id="status-2" /> `status` | [`ConsentStatus`](#consentstatus) | - | [`ConsentState`](#consentstate-1).[`status`](#status-3) |

***

### ConsentState

The persisted decision. Both flags are `false` while `undecided`, so a
consumer that only reads the flags gets the safe answer without checking
the status first.

#### Extends

* [`ConsentChoice`](#consentchoice)

#### Extended by

* [`ConsentSnapshot`](#consentsnapshot)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="analytics-4" /> `analytics` | `boolean` | - | [`ConsentChoice`](#consentchoice).[`analytics`](#analytics-1) |
| <a id="decidedat-1" /> `decidedAt` | `string` \| `null` | ISO 8601, when the shopper decided. Null while undecided. | - |
| <a id="marketing-3" /> `marketing` | `boolean` | - | [`ConsentChoice`](#consentchoice).[`marketing`](#marketing) |
| <a id="revision-5" /> `revision` | `number` | The policy revision the decision was made against. Bump the configured revision when the cookie policy changes materially and every shopper is asked again; a stored decision for an older revision reads as undecided. | - |
| <a id="status-3" /> `status` | [`ConsentStatus`](#consentstatus) | - | - |

***

### ConsentStoreOptions

#### Extends

* [`ConsentCookieWriteOptions`](#consentcookiewriteoptions)

#### Extended by

* [`ConsentProviderProps`](/kit/api/react#consentproviderprops)
* [`ElementsConsentOptions`](/kit/api/elements#elementsconsentoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="cookiename-3" /> `cookieName?` | `string` | Defaults to `qb_consent`. | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`cookieName`](#cookiename-2) |
| <a id="domain-1" /> `domain?` | `string` | Share the decision across subdomains (`.myshop.com`). Host-only by default. | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`domain`](#domain) |
| <a id="initialstate" /> `initialState?` | [`ConsentState`](#consentstate-1) \| `null` | The state to start from, instead of reading the cookie. For a server render: `readConsentCookie(cookieHeader)` on the server, passed down, so the first paint already knows the decision. Also what a test uses. `null` means "start undecided, do not read the cookie". | - |
| <a id="maxagedays-1" /> `maxAgeDays?` | `number` | Defaults to 180 days. | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`maxAgeDays`](#maxagedays) |
| <a id="persist" /> `persist?` | `boolean` | Write the decision to the cookie. Default true. Set false when another system owns persistence — a third-party consent platform whose callback calls `save()` on this store, or a test. | - |
| <a id="revision-6" /> `revision?` | `number` | The revision a stored decision must match to count. Defaults to 1. | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`revision`](#revision-3) |
| <a id="samesite-1" /> `sameSite?` | `"lax"` \| `"strict"` \| `"none"` | Defaults to `lax`. | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`sameSite`](#samesite) |
| <a id="secure-1" /> `secure?` | `boolean` | Defaults to true on https origins, false otherwise (localhost works). | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions).[`secure`](#secure) |

***

### CookieAccessor

The three cookie operations shopkit needs from a server framework. Model it
on whatever the host gives you — Next's `cookies()`, a Hono context, an
Express `req`/`res` pair.

`set`/`remove` are optional: a read-only accessor (an RSC, which cannot
mutate cookies) is a legitimate, common case. Writes are then dropped rather
than throwing, so rendering a server component never crashes just because it
touched the cart.

#### Methods

##### get()

```ts theme={null}
get(name): MaybePromise<string | null | undefined>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |

###### Returns

[`MaybePromise`](#maybepromise)\<`string` | `null` | `undefined`>

##### getAll()?

```ts theme={null}
optional getAll(): MaybePromise<readonly object[]>;
```

Every cookie on the request, as Next's `cookies().getAll()` returns them.
Optional, and read by one feature only: the checkout handoff looks for
Google Analytics' `_ga` / `_ga_<property>` cookies (whose names it cannot
know in advance) to stitch the storefront's GA session to the hosted
checkout's. Without it the consent decision is still forwarded and the
GA ids are not.

###### Returns

[`MaybePromise`](#maybepromise)\<readonly `object`\[]>

##### remove()?

```ts theme={null}
optional remove(name, attributes): MaybePromise<void>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |
| `attributes` | [`CookieAttributes`](#cookieattributes) |

###### Returns

[`MaybePromise`](#maybepromise)\<`void`>

##### set()?

```ts theme={null}
optional set(
   name, 
   value, 
attributes): MaybePromise<void>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |
| `value` | `string` |
| `attributes` | [`CookieAttributes`](#cookieattributes) |

###### Returns

[`MaybePromise`](#maybepromise)\<`void`>

***

### CookieAttributes

Cookie primitives with no dependencies, usable on both sides of the wire.

Kept deliberately small: shopkit only ever stores two opaque ids (the cart id
and the checkout session id), so there is no need for signing, chunking or
the full `cookie` package.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="domain-2" /> `domain?` | `string` | - |
| <a id="httponly" /> `httpOnly?` | `boolean` | Cookies written from the browser can never be httpOnly (the browser has no way to set it), so this only has meaning for a server-side adapter. |
| <a id="maxage" /> `maxAge?` | `number` | Seconds. Omitted → session cookie. |
| <a id="path-1" /> `path?` | `string` | Defaults to `/` so the id is visible to every route of the storefront. |
| <a id="samesite-2" /> `sameSite?` | `"lax"` \| `"strict"` \| `"none"` | Defaults to `lax` — the shopper returns from the hosted checkout via a top-level GET navigation, which `lax` allows and `strict` would drop. |
| <a id="secure-2" /> `secure?` | `boolean` | Defaults to true on https origins, false otherwise (so localhost works). |

***

### CreateSessionInput

Input to `checkout.createSession()`.

`successUrl` is REQUIRED unless `successMode` is `"inline"`; a redirect
session without one throws a `ShopkitConfigError` before any request.
`successMode` is sent only when set.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="backurl-2" /> `backUrl?` | `string` | Where the hosted checkout's "back to shop" / "continue shopping" links point — the header logo and back arrow, the empty-cart screen and the order confirmation. Same URL rule as `successUrl`: any `https` host, `http` only for `localhost`/loopback. When omitted the checkout falls back to the shop's storefront URL, then to the origin of `successUrl` — which for a headless storefront is usually the right page anyway. Set it when your storefront lives on a path or you want a specific landing page. In `inline` success mode it is the ONLY way home from the confirmation, so a headless or campaign storefront should set it to the page the shopper came from. |
| <a id="cancelurl-2" /> `cancelUrl?` | `string` | Where "back to cart" goes. |
| <a id="cartid-3" /> `cartId` | `string` | The cart to check out. |
| <a id="currency-8" /> `currency?` | `string` \| `null` | The currency the shopper is browsing in. Defaults to the client's currency (`client.currency`); pass `null` to send none. The shop decides what happens with it, exactly as for product and cart reads: a `"display"` currency leaves the session in the shop's currency and the response carries it as [CheckoutSession.displayCurrency](#displaycurrency); a `"charge"` currency prices and charges the session in it; anything else has no effect. Fixed when the session is created — a session for the same cart in a different currency is a new session. checkout-v2 only. |
| <a id="embed-2" /> `embed?` | [`SessionEmbed`](#sessionembed) | Declare where this session may be framed. Required for an embedded checkout and ignored by the redirect one — see [SessionEmbed](#sessionembed). |
| <a id="language-2" /> `language?` | `string` | Checkout UI language ("sv", "en", "en-US"). Validation is lenient — a code the checkout cannot translate falls back to the shop default rather than failing. |
| <a id="prefill" /> `prefill?` | [`SessionPrefill`](#sessionprefill) | - |
| <a id="storefrontid-3" /> `storefrontId?` | `string` | The campaign storefront this checkout is started from — `sf_…`. Binds the session to the campaign: its prices apply, it must be live, its quantity limits are enforced, and its id goes on the order. Falls back to the client's `storefront` binding; when neither is set the field is not sent, and a cart already bound to a campaign passes its binding on server-side. With an explicit `cartId` any campaign may be named. Without one (`checkout.start()` / `buyNow()` on the remembered cart) it must match the client's binding — see `StorefrontAttribution`. |
| <a id="successmode-2" /> `successMode?` | `"redirect"` \| `"inline"` | `"redirect"` (the default when omitted) or `"inline"`. See [SuccessMode](#successmode-7). |
| <a id="successurl-2" /> `successUrl?` | `string` | Where the shopper lands after paying. Required unless `successMode: "inline"`, where the checkout never redirects. Any `https` URL is accepted — the host needs no registration and no relationship to the shop's platform URL. `http` is accepted only for `localhost` and loopback addresses; a `javascript:` URL, or any other scheme, is rejected with 400. Only the ORIGIN is used for the post-payment redirect: the hosted checkout sends the shopper to `<origin>/success/<orderNumber>?hash=…&t=…`, so mount a route there (see `parseReturnUrl`). |
| <a id="surface-3" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` | Where the campaign was presented to this shopper. Only sent together with a `storefrontId`; defaults to the client's surface, then `"shopkit"`. |
| <a id="theme-1" /> `theme?` | `"light"` \| `"dark"` | Which theme the hosted checkout is painted in for this shopper. Set it when your own pages are dark, so the checkout does not flash white on the way in. Omitting it is not the same as passing `"light"`: omit it and the checkout follows the theme the merchant chose for their shop, pass `"light"` and this checkout stays light whatever the merchant chose. Fixed when the session is created and not changeable afterwards, so decide it with the rest of the page's theme. If you omit it, the merchant's choice applies live — a shopper with the checkout already open sees it change when the merchant changes it. |

***

### CurrencyInfo

What a currency choice means for one shop. See [describeCurrency](#describecurrency).

#### Extended by

* [`UseCurrencyResult`](/kit/api/react#usecurrencyresult)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="basecurrency-2" /> `baseCurrency` | `string` \| `null` | The shop's own (base) currency, or null when the shop is not known yet. |
| <a id="chargecurrency-1" /> `chargeCurrency` | `string` \| `null` | What the checkout will charge: the base currency, or `currency` in charge mode. |
| <a id="currencies" /> `currencies` | [`ShopCurrency`](#shopcurrency-1)\[] | Every currency the shop offers, base first. Empty until the shop is known. |
| <a id="currency-9" /> `currency` | `string` \| `null` | The currency prices are shown in: the chosen code when the shop offers it, else the shop's own currency. Null only before the shop is known and with nothing chosen. |
| <a id="mode-1" /> `mode` | [`ShopCurrencyMode`](#shopcurrencymode-1) | - `"base"` — the shop's own currency; nothing is converted. - `"display"` — prices are SHOWN converted, and the checkout charges the shop's currency (with the converted amount as an approximation). - `"charge"` — priced and charged in `currency`. |
| <a id="rate" /> `rate` | `number` | Units of `currency` per 1 unit of the base currency. 1 for `"base"`. |

***

### DataState

A server-computed value derived from the fields (prices, shipping options).

#### Type Parameters

| Type Parameter | Default type |
| - | - |
| `T` | `unknown` |

#### Properties

| Property | Type |
| - | - |
| <a id="resolvedat" /> `resolvedAt` | `string` \| `null` |
| <a id="value-1" /> `value` | `T` \| `null` |

***

### DestinationsFromShopOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="google" /> `google?` | `Omit`\<[`GoogleTagOptions`](#googletagoptions), `"ga4MeasurementId"` \| `"gtmContainerId"` \| `"adsConversionId"`> | Passed through to the Google destination. |
| <a id="loadbeforeconsent" /> `loadBeforeConsent?` | `boolean` | Load GTM / gtag.js before a decision, in Consent Mode denied. Default false. Shorthand for `google: { loadBeforeConsent }`. |

***

### DisplayCurrency

The currency a checkout may show an APPROXIMATE amount in, next to the real
prices — set when the session was created with a `"display"` currency.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="code" /> `code` | `string` | Upper-case ISO 4217. |
| <a id="rate-1" /> `rate` | `number` | Units of `code` per 1 unit of the currency the session is charged in. |

***

### EmbeddedCheckoutEventMap

#### Properties

| Property | Type |
| - | - |
| <a id="complete" /> `complete` | `object` |
| `complete.orderNumber` | `number` \| `null` |
| `complete.sessionId` | `string` |
| `complete.successUrl` | `string` \| `null` |
| <a id="demo-complete" /> `demo-complete` | `object` |
| `demo-complete.configureUrl` | `string` \| `null` |
| `demo-complete.sessionId` | `string` |
| <a id="error-2" /> `error` | `object` |
| `error.code` | [`EmbedErrorCode`](#embederrorcode) |
| `error.fatal` | `boolean` |
| `error.message` | `string` |
| <a id="event" /> `event` | `object` |
| `event.name` | `string` |
| `event.params` | `Record`\<`string`, `unknown`> |
| <a id="fallback" /> `fallback` | `object` |
| `fallback.reason` | [`EmbeddedCheckoutFallbackReason`](#embeddedcheckoutfallbackreason) |
| `fallback.url` | `string` \| `null` |
| <a id="height" /> `height` | `object` |
| `height.height` | `number` |
| <a id="navigate" /> `navigate` | `object` |
| `navigate.reason` | [`EmbedNavigateReason`](#embednavigatereason) |
| `navigate.url` | `string` |
| <a id="ready" /> `ready` | `object` |
| `ready.sessionId` | `string` |
| `ready.shopId` | `string` |
| `ready.step` | [`EmbedStep`](#embedstep) |
| <a id="redirect" /> `redirect` | `object` |
| `redirect.data` | `Record`\<`string`, `string`> |
| `redirect.method` | [`EmbedRedirectMethod`](#embedredirectmethod) |
| `redirect.url` | `string` |
| <a id="scroll-to" /> `scroll-to` | `object` |
| `scroll-to.top` | `number` |
| <a id="step" /> `step` | `object` |
| `step.step` | [`EmbedStep`](#embedstep) |

***

### EmbeddedCheckoutInitEmbed

Presentation and default-behaviour options, shared by `mount` and `resume`.

#### Extends

* [`EmbedRenderOptions`](#embedrenderoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="checkoutorigin" /> `checkoutOrigin` | `string` | Origin of `url`; the only origin messages are accepted from or sent to. | - |
| <a id="confirmation-2" /> `confirmation?` | `"redirect"` \| `"inline"` | What happens when the order completes. - `"redirect"` (default) — leave the page for the confirmed order, at exactly the URL the redirect checkout would have sent the shopper to (`<successUrl origin>/success/<orderId>?hash=…`), so one thank-you page serves both paths. - `"inline"` — stay on the page and let the frame render its own confirmation. | [`EmbedRenderOptions`](#embedrenderoptions).[`confirmation`](#confirmation-3) |
| <a id="container" /> `container` | `Element` | The element the iframe is appended to. | - |
| <a id="fallbackredirect" /> `fallbackRedirect?` | `boolean` | Navigate to the hosted checkout when there is nothing to frame, and when the frame that was built never answers. On by default — a shopper must never be left looking at an empty box because a shop is configured differently than the page assumed, or because this page is not the origin the session was created for. Turn it off to render your own message from the `fallback` event; its `url` is where the shopper should go. | [`EmbedRenderOptions`](#embedrenderoptions).[`fallbackRedirect`](#fallbackredirect-1) |
| <a id="fallbackurl" /> `fallbackUrl` | `string` \| `null` | The hosted (redirect) checkout URL for the same session — where the shopper is sent if the frame never answers. Null only when the caller has no way of knowing it, in which case the `handshake-timeout` fallback is reported but nobody is navigated anywhere. | - |
| <a id="handlenavigation" /> `handleNavigation?` | `boolean` | Perform the contract's default navigations (`navigate`, `redirect`, `complete`). Off means the handle only reports; YOU must then perform the redirect break-out yourself or redirect payment methods will not work. Defaults to on. | [`EmbedRenderOptions`](#embedrenderoptions).[`handleNavigation`](#handlenavigation-1) |
| <a id="handshaketimeoutms" /> `handshakeTimeoutMs?` | `number` | Override the 15s handshake deadline. For tests, chiefly. | [`EmbedRenderOptions`](#embedrenderoptions).[`handshakeTimeoutMs`](#handshaketimeoutms-1) |
| <a id="minheight" /> `minHeight?` | `number` | Height of the frame before the first `height` message, and the floor it never drops below. Set it to roughly the height of your checkout so the page does not jump on first paint. | [`EmbedRenderOptions`](#embedrenderoptions).[`minHeight`](#minheight-1) |
| <a id="mode-2" /> `mode` | `"embed"` | - | - |
| <a id="sessionid-2" /> `sessionId` | `string` \| `null` | - | - |
| <a id="successurl-3" /> `successUrl?` | `string` \| `null` | Fallback landing for a completed order, used only when the server sent none. The server's own URL — `<successUrl origin>/success/<orderId>?hash=…` — wins, because that is where the redirect checkout lands the same shopper. | - |
| <a id="title-1" /> `title?` | `string` | The iframe's accessible name. Defaults to `"Checkout"`. | [`EmbedRenderOptions`](#embedrenderoptions).[`title`](#title-2) |
| <a id="url" /> `url` | `string` | The `/embed/{shopId}/{sessionId}` URL. | - |

***

### EmbeddedCheckoutInitFallback

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="mode-3" /> `mode` | `"fallback"` | - |
| <a id="reason" /> `reason` | [`EmbeddedCheckoutFallbackReason`](#embeddedcheckoutfallbackreason) | - |
| <a id="redirect-1" /> `redirect` | `boolean` | Navigate to `url` after emitting. Off for a caller doing its own thing. |
| <a id="sessionid-3" /> `sessionId` | `string` \| `null` | - |
| <a id="url-1" /> `url` | `string` \| `null` | The hosted (redirect) checkout URL, when there is one. |

***

### EmbedRenderOptions

Presentation and default-behaviour options, shared by `mount` and `resume`.

#### Extended by

* [`EmbeddedCheckoutInitEmbed`](#embeddedcheckoutinitembed)
* [`MountCheckoutInput`](#mountcheckoutinput)
* [`ResumeCheckoutInput`](#resumecheckoutinput)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="confirmation-3" /> `confirmation?` | `"redirect"` \| `"inline"` | What happens when the order completes. - `"redirect"` (default) — leave the page for the confirmed order, at exactly the URL the redirect checkout would have sent the shopper to (`<successUrl origin>/success/<orderId>?hash=…`), so one thank-you page serves both paths. - `"inline"` — stay on the page and let the frame render its own confirmation. |
| <a id="fallbackredirect-1" /> `fallbackRedirect?` | `boolean` | Navigate to the hosted checkout when there is nothing to frame, and when the frame that was built never answers. On by default — a shopper must never be left looking at an empty box because a shop is configured differently than the page assumed, or because this page is not the origin the session was created for. Turn it off to render your own message from the `fallback` event; its `url` is where the shopper should go. |
| <a id="handlenavigation-1" /> `handleNavigation?` | `boolean` | Perform the contract's default navigations (`navigate`, `redirect`, `complete`). Off means the handle only reports; YOU must then perform the redirect break-out yourself or redirect payment methods will not work. Defaults to on. |
| <a id="handshaketimeoutms-1" /> `handshakeTimeoutMs?` | `number` | Override the 15s handshake deadline. For tests, chiefly. |
| <a id="minheight-1" /> `minHeight?` | `number` | Height of the frame before the first `height` message, and the floor it never drops below. Set it to roughly the height of your checkout so the page does not jump on first paint. |
| <a id="title-2" /> `title?` | `string` | The iframe's accessible name. Defaults to `"Checkout"`. |

***

### EmbedViewport

The host's visible region, as the frame needs to see it.

This is what lets a modal or a 3-D Secure overlay position itself inside the
visible slice of a 2000px-tall auto-sized frame instead of at the frame's own
centre, which can be far off screen.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="frametop" /> `frameTop` | `number` | The iframe's top edge relative to the host viewport. |
| <a id="height-1" /> `height` | `number` | - |
| <a id="scrolly" /> `scrollY` | `number` | Host scroll offset. |
| <a id="width" /> `width` | `number` | Host viewport size. |

***

### FieldState

A shopper-supplied value plus the server's verdict on it.

#### Type Parameters

| Type Parameter | Default type |
| - | - |
| `T` | `unknown` |

#### Properties

| Property | Type |
| - | - |
| <a id="errors" /> `errors` | `string`\[] |
| <a id="status-4" /> `status` | [`FieldStatus`](#fieldstatus) |
| <a id="updatedat-1" /> `updatedAt` | `string` \| `null` |
| <a id="value-2" /> `value` | `T` \| `null` |

***

### FormatMoneyOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="locale" /> `locale?` | `string` | BCP 47 locale for the number formatting — `"sv-SE"` gives `1 299,00 kr`, `"en-US"` gives `SEK 1,299.00`. Defaults to the runtime's own locale, which is the browser's in the one place this is normally called. On a server that is whatever `NODE_ICU` decided, so pass one explicitly if the output is user-visible there. |
| <a id="symbol" /> `symbol?` | `boolean` | Render the bare number with no currency symbol (`1 299,00`), for a layout that puts the symbol in its own element. |

***

### GaLink

The Google Analytics ids a page's `_ga` / `_ga_<property>` cookies carry,
for stitching the storefront session to the checkout's. The regexes are
the hosted storefront's (`cart.php`), so both surfaces extract the same
values from the same cookies.

#### Properties

| Property | Type |
| - | - |
| <a id="clientid" /> `clientId` | `string` \| `null` |
| <a id="sessionid-4" /> `sessionId` | `string` \| `null` |

***

### GoogleTagOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="adsconversionid" /> `adsConversionId?` | `string` \| `null` | `AW-XXXXXXXXX`. Configured on the same gtag.js as GA4. |
| <a id="ga4measurementid" /> `ga4MeasurementId?` | `string` \| `null` | `G-XXXXXXX`. Loads gtag.js and configures the property. |
| <a id="galink-1" /> `gaLink?` | \| \{ `clientId?`: `string` \| `null`; `sessionId?`: `string` \| `null`; } \| `null` | GA ids to stitch this page to an existing session (rare; the checkout uses it). |
| <a id="gtmcontainerid" /> `gtmContainerId?` | `string` \| `null` | `GTM-XXXXXXX`. Loads the container; events go to the dataLayer. |
| <a id="loadbeforeconsent-1" /> `loadBeforeConsent?` | `boolean` | Load GTM / gtag.js before the shopper decides, in Consent Mode with everything denied: Google's recommended "advanced" mode, where the tags send cookieless pings and model conversions. Default false: nothing from Google is injected until `analytics` is granted, and the denied `default` then the `update` are still pushed ahead of the script. Turn it on deliberately: a GTM container loaded this way runs ALL of its tags before a decision, gated only by how each tag reads Consent Mode. |
| <a id="sendpageview" /> `sendPageView?` | `boolean` | Let gtag.js send its automatic `page_view` on load. Default true. A single-page app that tracks its own navigations sets this false and calls `analytics.trackPageView()` on every route change instead. |

***

### HandoffParamsOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cookies" /> `cookies?` | `string` \| [`CookieList`](#cookielist) \| `null` | The cookies to read the GA ids from: a `Cookie:` header, or the `{ name, value }` list a framework's cookie jar returns. Defaults to `document.cookie` in a browser; pass the request's on a server. |
| <a id="linkga" /> `linkGa?` | `boolean` | Set false to leave the GA cookies alone. Default true. |
| <a id="requireconsent-2" /> `requireConsent?` | `boolean` | False means the storefront runs without consent and the checkout should track ungated, the way a hosted shop without the consent app does: no `consentCategories` is sent at all. Default true. |

***

### HostedCheckoutReturn

What the hosted checkout appends when it sends the shopper back after a
completed order: `/success/<orderNumber>?hash=…&t=…`.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="hash" /> `hash` | `string` \| `null` | Legacy integrity hash. Informational — confirm via `confirmation()`. |
| <a id="ordernumber" /> `orderNumber` | `number` | - |
| <a id="timestamp" /> `timestamp` | `number` \| `null` | Unix seconds the redirect was issued. |

***

### HostedCheckoutUrlInput

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="language-3" /> `language?` | `string` | Preselect the checkout language, flash-free on first paint. |
| <a id="sessionid-5" /> `sessionId` | `string` | - |
| <a id="shopid-1" /> ~~`shopId?`~~ | `string` \| `number` | **Deprecated** Renamed to [storeId](#storeid-3) in 1.0.0-beta.4. Up to beta.3 this defaulted to the key's shop prefix, which the hosted checkout does not recognise; the value must be the shop's numeric id (`Cart.storeId`) and anything else — `shopkit.shopId`/`shopPrefix` included — is rejected with a `ShopkitConfigError`. Read only when `storeId` is absent; removed before 1.0.0. |
| <a id="storeid-3" /> `storeId?` | `string` \| `number` | The shop's NUMERIC id — `Cart.storeId` — which is what the hosted checkout's path wants (`/checkout/101928/…`). Not the prefix in the publishable key (`101928Y`, `shopkit.shopPrefix`): the checkout does not recognise that value. Optional because shopkit remembers it from every cart the client receives. When neither is available `hostedUrl()` throws rather than guess. |

***

### ImageSrcSetOptions

Resize/encode parameters for Quickbutik's image CDN (imgix-compatible).

Every field is optional and omitted params fall back to the CDN's own
defaults. In a local/rig environment images are served straight from S3,
which ignores these — the URLs stay valid, they just come back full-size.

#### Extends

* [`ProductImageUrlOptions`](#productimageurloptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="auto" /> `auto?` | `string` \| `null` | Automatic optimisations → `auto`. Defaults to `"format"`, which is what the platform's own storefront sends: it serves WebP/AVIF to browsers that advertise support and the original format to those that do not. Pass `null` to send no `auto` at all. | [`ProductImageUrlOptions`](#productimageurloptions).[`auto`](#auto-2) |
| <a id="background" /> `background?` | `string` | Padding colour for `fit: "fill"` → `bg`. Hex without the `#`. | [`ProductImageUrlOptions`](#productimageurloptions).[`background`](#background-2) |
| <a id="baseurl" /> `baseUrl?` | `string` \| `null` | Base URL for the shop's image storage, used only as a fallback when the API returned no absolute `url` — an older api-core, or a fixture. It must be the shop-scoped base *without* the `products/` segment, e.g. `https://cdn.quickbutik.com/images/<shopPrefix>`. Set it once as `imageBaseUrl` on the client config rather than per call site. | [`ProductImageUrlOptions`](#productimageurloptions).[`baseUrl`](#baseurl-1) |
| <a id="cachebust" /> `cacheBust?` | `boolean` | Append the image's `contentHash` as a `v` parameter so a replaced file is not served stale from the CDN. On by default; the hash changes only when the binary does, so it costs no cache hits. | [`ProductImageUrlOptions`](#productimageurloptions).[`cacheBust`](#cachebust-1) |
| <a id="crop" /> `crop?` | `string` | Crop anchor → `crop`, e.g. `"faces,center"`. Only read when `fit: "crop"`. | [`ProductImageUrlOptions`](#productimageurloptions).[`crop`](#crop-2) |
| <a id="densities" /> `densities?` | readonly `number`\[] | Pixel ratios, emitted as `x` descriptors (`2x`). Defaults to `[1, 2]` when a `width` is known and no `widths` were given. | - |
| <a id="dpr" /> `dpr?` | `number` | Device pixel ratio → `dpr`. Multiplies `w`/`h` CDN-side. For a responsive image prefer `densities` on `<ProductImage />`, which emits one candidate per ratio and lets the browser choose. | [`ProductImageUrlOptions`](#productimageurloptions).[`dpr`](#dpr-2) |
| <a id="fit" /> `fit?` | `"clip"` \| `"crop"` \| `"fill"` \| `"facearea"` \| `"max"` \| `"min"` \| `"scale"` | How the image fills `width`×`height` → `fit`. The CDN default is `clip`. | [`ProductImageUrlOptions`](#productimageurloptions).[`fit`](#fit-2) |
| <a id="format" /> `format?` | `string` | Output format → `fm`, e.g. `"webp"`, `"avif"`, `"jpg"`, `"png"`. | [`ProductImageUrlOptions`](#productimageurloptions).[`format`](#format-2) |
| <a id="height-2" /> `height?` | `number` | Rendered height in CSS pixels → `h`. | [`ProductImageUrlOptions`](#productimageurloptions).[`height`](#height-4) |
| <a id="quality" /> `quality?` | `number` | JPEG/WebP quality, 1–100 → `q`. | [`ProductImageUrlOptions`](#productimageurloptions).[`quality`](#quality-2) |
| <a id="width-1" /> `width?` | `number` | Rendered width in CSS pixels → `w`. | [`ProductImageUrlOptions`](#productimageurloptions).[`width`](#width-3) |
| <a id="widths" /> `widths?` | readonly `number`\[] | Explicit candidate widths, emitted as `w` descriptors (`600w`) — pair with a `sizes` attribute. Wins over `densities`. | - |

***

### ImageTransform

Resize/encode parameters for Quickbutik's image CDN (imgix-compatible).

Every field is optional and omitted params fall back to the CDN's own
defaults. In a local/rig environment images are served straight from S3,
which ignores these — the URLs stay valid, they just come back full-size.

#### Extended by

* [`ProductImageUrlOptions`](#productimageurloptions)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="auto-1" /> `auto?` | `string` \| `null` | Automatic optimisations → `auto`. Defaults to `"format"`, which is what the platform's own storefront sends: it serves WebP/AVIF to browsers that advertise support and the original format to those that do not. Pass `null` to send no `auto` at all. |
| <a id="background-1" /> `background?` | `string` | Padding colour for `fit: "fill"` → `bg`. Hex without the `#`. |
| <a id="crop-1" /> `crop?` | `string` | Crop anchor → `crop`, e.g. `"faces,center"`. Only read when `fit: "crop"`. |
| <a id="dpr-1" /> `dpr?` | `number` | Device pixel ratio → `dpr`. Multiplies `w`/`h` CDN-side. For a responsive image prefer `densities` on `<ProductImage />`, which emits one candidate per ratio and lets the browser choose. |
| <a id="fit-1" /> `fit?` | `"clip"` \| `"crop"` \| `"fill"` \| `"facearea"` \| `"max"` \| `"min"` \| `"scale"` | How the image fills `width`×`height` → `fit`. The CDN default is `clip`. |
| <a id="format-1" /> `format?` | `string` | Output format → `fm`, e.g. `"webp"`, `"avif"`, `"jpg"`, `"png"`. |
| <a id="height-3" /> `height?` | `number` | Rendered height in CSS pixels → `h`. |
| <a id="quality-1" /> `quality?` | `number` | JPEG/WebP quality, 1–100 → `q`. |
| <a id="width-2" /> `width?` | `number` | Rendered width in CSS pixels → `w`. |

***

### LegacyHandoff

A `legacy` handoff, and what does and does not carry over from `v2`.

**The shopper comes back, the same way as from `v2`.** After a completed
order the legacy checkout redirects to
`<successUrl origin>/success/<orderNumber>?t=…&hash=…` — only the ORIGIN of
your `successUrl` is used, exactly as on `v2`, so one thank-you route serves
both checkouts — and its back links go to your `backUrl`.
[CheckoutResource.parseReturnUrl](#parsereturnurl) reads that return, and the uuid
`start()` remembered is what `pollConfirmation()` (and
`useOrderConfirmation()` / `<qb-order-confirmation>` with no id given)
confirms the order by. `hash` is computed the same way on both checkouts. The
post-purchase-offer token (`&ppo=`) is only appended when `successUrl` is on
a host the shop owns, so a headless storefront on its own domain should not
expect one.

Up to kit 1.3 this said the opposite — the legacy checkout used to redirect
to the shop's own platform storefront and could not be pointed anywhere
else. The platform now honours the handoff's `successUrl` and `backUrl` on
both checkouts.

Three things that still differ from `v2`:

1. **`successMode: "inline"` does not apply.** The legacy checkout always
   redirects, so the platform refuses an inline handoff for a legacy shop
   with a 400 (`details.reason: "legacy_checkout_unsupported"`) before any
   order is created. Pass a real `successUrl`.
2. **There is no session.** `getSession()` and patching do not apply. Poll
   [CheckoutResource.confirmation](#confirmation) with [legacyOrderUuid](#legacyorderuuid) instead
   — the confirmation endpoint takes either handle.
3. **A failed payment reads as `no_attempt`, not `failed`.** The legacy
   checkout leaves a failed order looking untouched, so a `timeout` or
   `no_attempt` outcome is not evidence the shopper gave up. Rely on the
   merchant's own order confirmation email as the source of truth.

Cart-level campaign and säljplats override prices are also NOT carried over:
the legacy checkout re-prices every line from the catalog. A shop that sells
on campaign pricing needs checkout-v2.

Minor, and outside the storefront's control: the order records the IP the
platform saw for the caller, which for a headless storefront is a platform
address rather than the shopper's. Merchant-visible on the order; it affects
nothing about the handoff, the payment or the confirmation.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cartid-4" /> `cartId` | `string` | - |
| <a id="checkout-1" /> `checkout` | `"legacy"` | - |
| <a id="legacyorderuuid" /> `legacyOrderUuid` | `string` \| `null` | The unpaid order this handoff created — or reused, when the shopper has already been here with this cart. Also the handle to poll [CheckoutResource.confirmation](#confirmation) with. Null only in the pathological case of a legacy order carrying no uuid, which is also the only case where `hostedUrl` is null on a configured host. `start()` refuses that response rather than hand back a handoff nothing can confirm. |

***

### ListParams

#### Properties

| Property | Type |
| - | - |
| <a id="limit-1" /> `limit?` | `number` |
| <a id="offset" /> `offset?` | `number` |

***

### MetaPixelOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="pixelid" /> `pixelId` | `string` | The numeric pixel id from the shop's tracking configuration. |

***

### MountCheckoutInput

Input to `checkout.start()`: a `CreateSessionInput` whose cart is optional.
The `successUrl` rule is the same — required unless `successMode: "inline"`.

#### Extends

* [`StartCheckoutInput`](#startcheckoutinput).[`EmbedRenderOptions`](#embedrenderoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="backurl-3" /> `backUrl?` | `string` | Where the hosted checkout's "back to shop" / "continue shopping" links point — the header logo and back arrow, the empty-cart screen and the order confirmation. Same URL rule as `successUrl`: any `https` host, `http` only for `localhost`/loopback. When omitted the checkout falls back to the shop's storefront URL, then to the origin of `successUrl` — which for a headless storefront is usually the right page anyway. Set it when your storefront lives on a path or you want a specific landing page. In `inline` success mode it is the ONLY way home from the confirmation, so a headless or campaign storefront should set it to the page the shopper came from. | [`CreateSessionInput`](#createsessioninput).[`backUrl`](#backurl-2) |
| <a id="cancelurl-3" /> `cancelUrl?` | `string` | Where "back to cart" goes. | [`CreateSessionInput`](#createsessioninput).[`cancelUrl`](#cancelurl-2) |
| <a id="cartid-5" /> `cartId?` | `string` | Defaults to the remembered cart (created if this visitor has none). | [`StartCheckoutInput`](#startcheckoutinput).[`cartId`](#cartid-6) |
| <a id="confirmation-4" /> `confirmation?` | `"redirect"` \| `"inline"` | What happens when the order completes. - `"redirect"` (default) — leave the page for the confirmed order, at exactly the URL the redirect checkout would have sent the shopper to (`<successUrl origin>/success/<orderId>?hash=…`), so one thank-you page serves both paths. - `"inline"` — stay on the page and let the frame render its own confirmation. | [`EmbedRenderOptions`](#embedrenderoptions).[`confirmation`](#confirmation-3) |
| <a id="currency-10" /> `currency?` | `string` \| `null` | The currency the shopper is browsing in. Defaults to the client's currency (`client.currency`); pass `null` to send none. The shop decides what happens with it, exactly as for product and cart reads: a `"display"` currency leaves the session in the shop's currency and the response carries it as [CheckoutSession.displayCurrency](#displaycurrency); a `"charge"` currency prices and charges the session in it; anything else has no effect. Fixed when the session is created — a session for the same cart in a different currency is a new session. checkout-v2 only. | [`CreateSessionInput`](#createsessioninput).[`currency`](#currency-8) |
| <a id="embed-3" /> `embed?` | [`SessionEmbed`](#sessionembed) | Declare where this session may be framed. Required for an embedded checkout and ignored by the redirect one — see [SessionEmbed](#sessionembed). | [`CreateSessionInput`](#createsessioninput).[`embed`](#embed-2) |
| <a id="embedorigin" /> `embedOrigin?` | `string` | The origin recorded on the session as the only one allowed to frame it. Defaults to this page's. Override only when the page creating the session is not the page that will show it. | - |
| <a id="fallbackredirect-2" /> `fallbackRedirect?` | `boolean` | Navigate to the hosted checkout when there is nothing to frame, and when the frame that was built never answers. On by default — a shopper must never be left looking at an empty box because a shop is configured differently than the page assumed, or because this page is not the origin the session was created for. Turn it off to render your own message from the `fallback` event; its `url` is where the shopper should go. | [`EmbedRenderOptions`](#embedrenderoptions).[`fallbackRedirect`](#fallbackredirect-1) |
| <a id="handlenavigation-2" /> `handleNavigation?` | `boolean` | Perform the contract's default navigations (`navigate`, `redirect`, `complete`). Off means the handle only reports; YOU must then perform the redirect break-out yourself or redirect payment methods will not work. Defaults to on. | [`EmbedRenderOptions`](#embedrenderoptions).[`handleNavigation`](#handlenavigation-1) |
| <a id="handshaketimeoutms-2" /> `handshakeTimeoutMs?` | `number` | Override the 15s handshake deadline. For tests, chiefly. | [`EmbedRenderOptions`](#embedrenderoptions).[`handshakeTimeoutMs`](#handshaketimeoutms-1) |
| <a id="language-4" /> `language?` | `string` | Checkout UI language ("sv", "en", "en-US"). Validation is lenient — a code the checkout cannot translate falls back to the shop default rather than failing. | [`CreateSessionInput`](#createsessioninput).[`language`](#language-2) |
| <a id="minheight-2" /> `minHeight?` | `number` | Height of the frame before the first `height` message, and the floor it never drops below. Set it to roughly the height of your checkout so the page does not jump on first paint. | [`EmbedRenderOptions`](#embedrenderoptions).[`minHeight`](#minheight-1) |
| <a id="prefill-1" /> `prefill?` | [`SessionPrefill`](#sessionprefill) | - | [`CreateSessionInput`](#createsessioninput).[`prefill`](#prefill) |
| <a id="returnurl" /> `returnUrl?` | `string` | Where a redirect payment method returns the shopper. Defaults to this page's URL with the kit's own parameters stripped, so a return does not stack `?qb_checkout_session=` and `qb_checkout_shop=` twice. Must share an origin with `embedOrigin`. | - |
| <a id="storefrontid-4" /> `storefrontId?` | `string` | The campaign storefront this checkout is started from — `sf_…`. Binds the session to the campaign: its prices apply, it must be live, its quantity limits are enforced, and its id goes on the order. Falls back to the client's `storefront` binding; when neither is set the field is not sent, and a cart already bound to a campaign passes its binding on server-side. With an explicit `cartId` any campaign may be named. Without one (`checkout.start()` / `buyNow()` on the remembered cart) it must match the client's binding — see `StorefrontAttribution`. | [`CreateSessionInput`](#createsessioninput).[`storefrontId`](#storefrontid-3) |
| <a id="successmode-3" /> `successMode?` | `"redirect"` \| `"inline"` | `"redirect"` (the default when omitted) or `"inline"`. See [SuccessMode](#successmode-7). | [`CreateSessionInput`](#createsessioninput).[`successMode`](#successmode-2) |
| <a id="successurl-4" /> `successUrl?` | `string` | Where the shopper lands after paying. Required unless `successMode: "inline"`, where the checkout never redirects. Any `https` URL is accepted — the host needs no registration and no relationship to the shop's platform URL. `http` is accepted only for `localhost` and loopback addresses; a `javascript:` URL, or any other scheme, is rejected with 400. Only the ORIGIN is used for the post-payment redirect: the hosted checkout sends the shopper to `<origin>/success/<orderNumber>?hash=…&t=…`, so mount a route there (see `parseReturnUrl`). | [`CreateSessionInput`](#createsessioninput).[`successUrl`](#successurl-2) |
| <a id="surface-4" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` | Where the campaign was presented to this shopper. Only sent together with a `storefrontId`; defaults to the client's surface, then `"shopkit"`. | [`CreateSessionInput`](#createsessioninput).[`surface`](#surface-3) |
| <a id="theme-2" /> `theme?` | `"light"` \| `"dark"` | Which theme the hosted checkout is painted in for this shopper. Set it when your own pages are dark, so the checkout does not flash white on the way in. Omitting it is not the same as passing `"light"`: omit it and the checkout follows the theme the merchant chose for their shop, pass `"light"` and this checkout stays light whatever the merchant chose. Fixed when the session is created and not changeable afterwards, so decide it with the rest of the page's theme. If you omit it, the merchant's choice applies live — a shopper with the checkout already open sees it change when the merchant changes it. | [`CreateSessionInput`](#createsessioninput).[`theme`](#theme-1) |
| <a id="title-3" /> `title?` | `string` | The iframe's accessible name. Defaults to `"Checkout"`. | [`EmbedRenderOptions`](#embedrenderoptions).[`title`](#title-2) |

***

### NextLikeMetadata

Next.js `Metadata`, structurally.

Declared here rather than imported from `next`: shopkit must not depend on a
framework, and this is the subset `buildSeo` can fill. Assignable to Next's own
`Metadata` type at the call site.

#### Properties

| Property | Type |
| - | - |
| <a id="alternates" /> `alternates?` | `object` |
| `alternates.canonical?` | `string` |
| <a id="description-2" /> `description?` | `string` |
| <a id="opengraph" /> `openGraph?` | `object` |
| `openGraph.description?` | `string` |
| `openGraph.images?` | `object`\[] |
| `openGraph.locale?` | `string` |
| `openGraph.siteName?` | `string` |
| `openGraph.title?` | `string` |
| `openGraph.type?` | `string` |
| `openGraph.url?` | `string` |
| <a id="other" /> `other?` | `Record`\<`string`, `string`> |
| <a id="robots" /> `robots?` | `object` |
| `robots.follow` | `boolean` |
| `robots.index` | `boolean` |
| <a id="title-4" /> `title?` | `string` |
| <a id="twitter" /> `twitter?` | `object` |
| `twitter.card?` | `"summary"` \| `"summary_large_image"` |
| `twitter.creator?` | `string` |
| `twitter.description?` | `string` |
| `twitter.images?` | `string`\[] |
| `twitter.site?` | `string` |
| `twitter.title?` | `string` |

***

### OrderLine

A line the server added on top of the products (shipping, fee, discount).

#### Properties

| Property | Type | Description | | | | |
| - | - | - | - | - | - | - |
| <a id="amount" /> `amount` | `number` | Signed: negative on discounts. | | | | |
| <a id="id-5" /> `id` | `string` | - | | | | |
| <a id="label" /> `label` | `string` | - | | | | |
| <a id="tag" /> `tag` | `string` | `shipping` | `payment` | `promocode` | `free_shipping` | … |
| <a id="taxamount-1" /> `taxAmount?` | `number` | - | | | | |
| <a id="taxrate-1" /> `taxRate?` | `number` | - | | | | |

***

### Page

Cursor-paginated collection, as the gateway returns it.

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Properties

| Property | Type |
| - | - |
| <a id="data-4" /> `data` | `T`\[] |
| <a id="has_more" /> `has_more` | `boolean` |
| <a id="next_cursor" /> `next_cursor` | `string` \| `null` |

***

### ParsedPublishableKey

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="key-1" /> `key` | `string` | The whole key, as it must be sent in `Authorization: Bearer …`. |
| <a id="shopprefix-1" /> `shopPrefix` | `string` | The shop's storage prefix (`101928Y`), as encoded in the key. It is the folder the shop's files live under on the CDN — `https://cdn.quickbutik.com/images/<shopPrefix>/products/<file>` — and the value an `imageBaseUrl` is built from. It is NOT the shop's numeric id: the hosted checkout URL wants that one (`/checkout/101928/…`), and it arrives on every cart as `Cart.storeId`. |

***

### PatchResult

What a `PATCH /v2/checkout/sessions/:id` answers with — the fields and data
after the change, plus what the change invalidated.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="data-5" /> `data` | [`SessionDataMap`](#sessiondatamap) | - |
| <a id="fields-2" /> `fields` | [`SessionFieldMap`](#sessionfieldmap) | - |
| <a id="invalidatedfields" /> `invalidatedFields` | `string`\[] | Fields whose value is now stale because a dependency changed. |
| <a id="iscomplete-1" /> `isComplete` | `boolean` | - |
| <a id="resolveddata" /> `resolvedData` | `string`\[] | Data nodes that were re-resolved as a result. |

***

### PickProductImageOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="imageid" /> `imageId?` | `number` | Pick by image id instead of by position. Wins over `index`. |
| <a id="includepending" /> `includePending?` | `boolean` | Include images whose binary is still being processed. Off by default: a pending image's URL 404s until the ingest job finishes. |
| <a id="index" /> `index?` | `number` | Position in display order. Defaults to 0 — the main image. |

***

### PollConfirmationOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="maxpolls" /> `maxPolls?` | `number` | Hard cap on polls. Default 20. |
| <a id="maxtotalms" /> `maxTotalMs?` | `number` | Hard cap on total wall time in ms. Default 90000. |
| <a id="onupdate" /> `onUpdate?` | (`confirmation`) => `void` | Called after every poll, for a live "creating your order…" UI. |
| <a id="signal" /> `signal?` | `AbortSignal` | - |

***

### Product

A product as the storefront sees it — visible products only, no cost prices,
no supplier data. Served by api-core's storefront controller, which is the
only products surface a publishable key can reach.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="currency-11" /> `currency?` | `string` | The currency every price on this product is in — the client's chosen currency when the shop offers it, else the shop's own. Format with this, never a hardcoded code. Absent from a platform older than the field. |
| <a id="description-3" /> `description` | `string` \| `null` | - |
| <a id="id-6" /> `id` | `string` | Prefixed id (`prod_123`). Pass it straight back to `cart.addItem` — the gateway strips the prefix on the way to api-core. |
| <a id="images" /> `images` | [`ProductImage`](#productimage)\[] | - |
| <a id="name-4" /> `name` | `string` \| `null` | - |
| <a id="options" /> `options?` | [`ProductOptionType`](#productoptiontype)\[] | - |
| <a id="price-2" /> `price` | `number` \| `null` | Display price of the product, in minor units. |
| <a id="relatedproducts" /> `relatedProducts?` | [`RelatedProducts`](#relatedproducts-1) | The product's related products. Served **only** by a single-product read (`products.get`); absent on `products.list` and search results. |
| <a id="sections" /> `sections?` | [`ProductSection`](#productsection)\[] | Merchant-authored content sections, in the merchant's order. Empty when the product has none; absent from responses of API deployments that predate the field. |
| <a id="seodescription" /> `seoDescription` | `string` \| `null` | - |
| <a id="seotitle-1" /> `seoTitle` | `string` \| `null` | - |
| <a id="slug-2" /> `slug` | `string` \| `null` | - |
| <a id="taxrate-2" /> `taxRate` | `number` \| `null` | VAT percentage (25, 12, 6 …). |
| <a id="variants" /> `variants` | [`ProductVariant`](#productvariant)\[] | - |
| <a id="visible" /> `visible` | `boolean` \| `null` | - |

***

### ProductControllerOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="initialvariantid" /> `initialVariantId?` | `number` | Preselect this variant — a deep link, or a "recently viewed" restore. |
| <a id="selectfirstavailable" /> `selectFirstAvailable?` | `boolean` | Preselect the first purchasable variant instead of starting empty. Off by default, for the same reason React's `ProductProvider` defaults it off: an empty start shows a price RANGE, which is honest for a product whose variants differ in price, and it does not put words in the shopper's mouth about which colour they wanted. |

***

### ProductImage

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="alttext" /> `altText` | `string` \| `null` | - |
| <a id="contenthash" /> `contentHash` | `string` \| `null` | Hash of the binary, appended as a cache-buster by `resolveProductImageUrl`. The literal `"temp"` marks an image whose file is still being processed. |
| <a id="date" /> `date` | `string` \| `null` | - |
| <a id="id-7" /> `id` | `number` | - |
| <a id="path-2" /> `path` | `string` \| `null` | The bare storage filename (`5c7b0e7e1802c.jpeg`). **Not** a URL: it is missing the shop's storage prefix, which no storefront credential can see, so `<img src={image.path}>` resolves against your own origin and 404s. Use `url` instead. Kept because the field is part of the existing contract and identifies the file. |
| <a id="position" /> `position` | `number` \| `null` | - |
| <a id="productid-4" /> `productId` | `number` | - |
| <a id="url-2" /> `url` | `string` \| `null` | Absolute URL of the image file, with **no** resize parameters — the only renderable value here. Add the pixels yourself (`?w=&h=`, imgix-compatible) or let `resolveProductImageUrl` / `<ProductImage />` do it. Null when the image has no file yet. |

***

### ProductImageUrlOptions

Resize/encode parameters for Quickbutik's image CDN (imgix-compatible).

Every field is optional and omitted params fall back to the CDN's own
defaults. In a local/rig environment images are served straight from S3,
which ignores these — the URLs stay valid, they just come back full-size.

#### Extends

* [`ImageTransform`](#imagetransform)

#### Extended by

* [`ImageSrcSetOptions`](#imagesrcsetoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="auto-2" /> `auto?` | `string` \| `null` | Automatic optimisations → `auto`. Defaults to `"format"`, which is what the platform's own storefront sends: it serves WebP/AVIF to browsers that advertise support and the original format to those that do not. Pass `null` to send no `auto` at all. | [`ImageTransform`](#imagetransform).[`auto`](#auto-1) |
| <a id="background-2" /> `background?` | `string` | Padding colour for `fit: "fill"` → `bg`. Hex without the `#`. | [`ImageTransform`](#imagetransform).[`background`](#background-1) |
| <a id="baseurl-1" /> `baseUrl?` | `string` \| `null` | Base URL for the shop's image storage, used only as a fallback when the API returned no absolute `url` — an older api-core, or a fixture. It must be the shop-scoped base *without* the `products/` segment, e.g. `https://cdn.quickbutik.com/images/<shopPrefix>`. Set it once as `imageBaseUrl` on the client config rather than per call site. | - |
| <a id="cachebust-1" /> `cacheBust?` | `boolean` | Append the image's `contentHash` as a `v` parameter so a replaced file is not served stale from the CDN. On by default; the hash changes only when the binary does, so it costs no cache hits. | - |
| <a id="crop-2" /> `crop?` | `string` | Crop anchor → `crop`, e.g. `"faces,center"`. Only read when `fit: "crop"`. | [`ImageTransform`](#imagetransform).[`crop`](#crop-1) |
| <a id="dpr-2" /> `dpr?` | `number` | Device pixel ratio → `dpr`. Multiplies `w`/`h` CDN-side. For a responsive image prefer `densities` on `<ProductImage />`, which emits one candidate per ratio and lets the browser choose. | [`ImageTransform`](#imagetransform).[`dpr`](#dpr-1) |
| <a id="fit-2" /> `fit?` | `"clip"` \| `"crop"` \| `"fill"` \| `"facearea"` \| `"max"` \| `"min"` \| `"scale"` | How the image fills `width`×`height` → `fit`. The CDN default is `clip`. | [`ImageTransform`](#imagetransform).[`fit`](#fit-1) |
| <a id="format-2" /> `format?` | `string` | Output format → `fm`, e.g. `"webp"`, `"avif"`, `"jpg"`, `"png"`. | [`ImageTransform`](#imagetransform).[`format`](#format-1) |
| <a id="height-4" /> `height?` | `number` | Rendered height in CSS pixels → `h`. | [`ImageTransform`](#imagetransform).[`height`](#height-3) |
| <a id="quality-2" /> `quality?` | `number` | JPEG/WebP quality, 1–100 → `q`. | [`ImageTransform`](#imagetransform).[`quality`](#quality-1) |
| <a id="width-3" /> `width?` | `number` | Rendered width in CSS pixels → `w`. | [`ImageTransform`](#imagetransform).[`width`](#width-2) |

***

### ProductJsonLdOptions

#### Properties

| Property | Type |
| - | - |
| <a id="brand" /> `brand?` | `string` |
| <a id="currency-12" /> `currency?` | `string` |
| <a id="images-1" /> `images?` | `string`\[] |
| <a id="url-3" /> `url?` | `string` |

***

### ProductListParams

Which campaign storefront prices a product read.

A client bound to a campaign (`ShopkitConfig.storefront` /
`storefrontId`) sends its id on every product read, so the catalog it shows
carries the same campaign prices its cart will charge — a product page that
says 199 kr above a cart that says 149 kr is the bug this closes. Pass one
here to price a single read for a different campaign; reads are stateless,
so any campaign may be named. `null` opts a bound client out for this call
and reads the shop's ordinary prices.

#### Extends

* [`RequestOptions`](#requestoptions).[`ProductReadOptions`](#productreadoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="currency-13" /> `currency?` | `string` \| `null` | The currency to price this read in. Defaults to the client's (`client.currency`); `null` reads the shop's own prices. The response's `product.currency` says what the prices actually came back in. | [`ProductReadOptions`](#productreadoptions).[`currency`](#currency-14) |
| <a id="cursor-1" /> `cursor?` | `string` | Opaque cursor from a previous page's `next_cursor`. | - |
| <a id="limit-2" /> `limit?` | `number` | Page size, 1–200. Defaults to the gateway's 50 when omitted. | - |
| <a id="signal-1" /> `signal?` | `AbortSignal` | Abort the underlying fetch (component unmount, route change). | [`RequestOptions`](#requestoptions).[`signal`](#signal-3) |
| <a id="storefrontid-5" /> `storefrontId?` | `string` \| `null` | The campaign storefront (`sf_…`) to price this read for. Defaults to the client's binding; `null` means none. A malformed id throws a `ShopkitConfigError` before the request goes out. | [`ProductReadOptions`](#productreadoptions).[`storefrontId`](#storefrontid-6) |

***

### ProductOptionType

A selectable option ("Size"), owned by the product.

#### Properties

| Property | Type |
| - | - |
| <a id="id-8" /> `id` | `number` |
| <a id="name-5" /> `name` | `string` \| `null` |
| <a id="position-1" /> `position` | `number` \| `null` |

***

### ProductOptionValue

One concrete choice of an option ("XL"), referenced by a variant.

#### Properties

| Property | Type |
| - | - |
| <a id="id-9" /> `id` | `number` |
| <a id="name-6" /> `name` | `string` \| `null` |
| <a id="optionid" /> `optionId` | `number` \| `null` |
| <a id="position-2" /> `position` | `number` \| `null` |

***

### ProductReadOptions

Which campaign storefront prices a product read.

A client bound to a campaign (`ShopkitConfig.storefront` /
`storefrontId`) sends its id on every product read, so the catalog it shows
carries the same campaign prices its cart will charge — a product page that
says 199 kr above a cart that says 149 kr is the bug this closes. Pass one
here to price a single read for a different campaign; reads are stateless,
so any campaign may be named. `null` opts a bound client out for this call
and reads the shop's ordinary prices.

#### Extended by

* [`ProductListParams`](#productlistparams)
* [`ProductSearchParams`](#productsearchparams)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="currency-14" /> `currency?` | `string` \| `null` | The currency to price this read in. Defaults to the client's (`client.currency`); `null` reads the shop's own prices. The response's `product.currency` says what the prices actually came back in. |
| <a id="storefrontid-6" /> `storefrontId?` | `string` \| `null` | The campaign storefront (`sf_…`) to price this read for. Defaults to the client's binding; `null` means none. A malformed id throws a `ShopkitConfigError` before the request goes out. |

***

### ProductSearchParams

Which campaign storefront prices a product read.

A client bound to a campaign (`ShopkitConfig.storefront` /
`storefrontId`) sends its id on every product read, so the catalog it shows
carries the same campaign prices its cart will charge — a product page that
says 199 kr above a cart that says 149 kr is the bug this closes. Pass one
here to price a single read for a different campaign; reads are stateless,
so any campaign may be named. `null` opts a bound client out for this call
and reads the shop's ordinary prices.

#### Extends

* [`RequestOptions`](#requestoptions).[`ProductReadOptions`](#productreadoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="categoryid-1" /> `categoryId?` | `string` \| `number` | Restrict to one category — `"cat_12"` or `12`, both accepted (a bare number is prefixed for you, because the gateway rejects it otherwise). Direct membership only — child categories are not walked, so filtering by a parent returns nothing that lives solely in its children. Read the ids from [CategoriesResource.list](#list), which the same scope reaches. | - |
| <a id="currency-15" /> `currency?` | `string` \| `null` | The currency to price this read in. Defaults to the client's (`client.currency`); `null` reads the shop's own prices. The response's `product.currency` says what the prices actually came back in. | [`ProductReadOptions`](#productreadoptions).[`currency`](#currency-14) |
| <a id="cursor-2" /> `cursor?` | `string` | Opaque cursor from a previous page's `next_cursor`. | - |
| <a id="limit-3" /> `limit?` | `number` | Page size, 1–200. Defaults to the gateway's 50 when omitted. | - |
| <a id="maxprice" /> `maxPrice?` | `number` | - | - |
| <a id="minprice" /> `minPrice?` | `number` | Inclusive price bounds in **minor units** — `20000` is 200,00 kr. **Always in the shop's own currency**, whatever currency the read is priced in: filtering and `sortBy: "price"` run on the stored prices. **These match the undiscounted list price.** Storefront discounts are applied per page after the query runs, so on a shop running a discount a product can come back priced outside the bounds you asked for. The same caveat applies to `sortBy: "price"`. An **inverted range** (`minPrice` above `maxPrice`) is rejected with a 400 rather than silently returning nothing. Order them, or swap them before calling. | - |
| <a id="search-3" /> `search?` | `string` | Free-text term, matched against product name, SKU and GTIN, and against variant SKU/GTIN and variant option values. Whitespace-separated words are AND-ed and order-independent, so "merino crew" matches a "Crew neck, merino" product. Only the first 10 words are considered. | - |
| <a id="signal-2" /> `signal?` | `AbortSignal` | Abort the underlying fetch (component unmount, route change). | [`RequestOptions`](#requestoptions).[`signal`](#signal-3) |
| <a id="sortby" /> `sortBy?` | [`ProductSortField`](#productsortfield) | Defaults to `createdAt` server-side. | - |
| <a id="sortorder" /> `sortOrder?` | `"desc"` \| `"asc"` | Defaults to `desc` server-side. | - |
| <a id="storefrontid-7" /> `storefrontId?` | `string` \| `null` | The campaign storefront (`sf_…`) to price this read for. Defaults to the client's binding; `null` means none. A malformed id throws a `ShopkitConfigError` before the request goes out. | [`ProductReadOptions`](#productreadoptions).[`storefrontId`](#storefrontid-6) |

***

### ProductSection

A merchant-authored content section of a product page ("Size guide",
"Care", "Delivery"), in the order the merchant arranged them.

Rendered server-side exactly as the Quickbutik theme renders it: a section
linked to a shared template already carries the template's text, and the
`[STOCKLEFT]`, `[PRICE]` and `[BEFOREPRICE]` merge tags are replaced.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="content" /> `content` | `string` | Merchant-authored **HTML**. May be empty when the section has a title only. Sanitize it before injecting it as HTML. |
| <a id="id-10" /> `id` | `string` | Stable across edits and unique within the product — a render key. |
| <a id="title-5" /> `title` | `string` | Heading. May be empty when the section has content only. |

***

### ProductSnapshot

Everything derived from one product plus the shopper's current choices.

The read half of [ProductController](#productcontroller), and the exact shape React's
`ProductState` is built from — the two must not drift, so the hook spreads
this rather than restating it.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="hasoptions" /> `hasOptions` | `boolean` | False for a simple product — render no picker. |
| <a id="iscomplete-2" /> `isComplete` | `boolean` | True once every option group has a choice (always true with no options). |
| <a id="options-1" /> `options` | [`VariantOptionGroupState`](#variantoptiongroupstate)\[] | Option groups with per-value `selected` / `available` flags, in display order. |
| <a id="price-3" /> `price` | [`VariantPriceState`](#variantpricestate) | Price for the current selection: exact figure, or a range before that. |
| <a id="product-2" /> `product` | [`Product`](#product-1) | - |
| <a id="selectedvariant" /> `selectedVariant` | [`ProductVariant`](#productvariant) \| `null` | The variant the selection identifies, or null while it is incomplete. |
| <a id="selection" /> `selection` | [`VariantSelection`](#variantselection) | The raw selection, keyed by option id. Partial until every group is chosen. |

***

### ProductVariant

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="barcode" /> `barcode` | `string` \| `null` | - |
| <a id="hidden" /> `hidden` | `boolean` \| `null` | - |
| <a id="id-11" /> `id` | `number` | Numeric — this is the id the Cart API takes as `variantId`. |
| <a id="imageid-1" /> `imageId?` | `number` \| `null` | The variant's own image: the `id` of one of the product's `images`, so `product.images.find((image) => image.id === variant.imageId)` resolves it — swap the gallery to it when the shopper picks this variant. Null when the variant has no image of its own (show the product's gallery), and always on the single synthetic variant of a simple product. Never points at an image that is not in `images`. Absent from responses of API deployments that predate the field. |
| <a id="maxpurchaseqty" /> `maxPurchaseQty` | `number` \| `null` | - |
| <a id="minpurchaseqty" /> `minPurchaseQty` | `number` \| `null` | - |
| <a id="options-2" /> `options` | [`ProductOptionValue`](#productoptionvalue)\[] | - |
| <a id="price-4" /> `price` | [`ProductVariantPrice`](#productvariantprice-1) | - |
| <a id="sku-1" /> `sku` | `string` | - |
| <a id="stock" /> `stock` | [`ProductVariantStock`](#productvariantstock-1) | - |
| <a id="weight-1" /> `weight` | `number` \| `null` | Grams. |

***

### ProductVariantPrice

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="compareprice" /> `comparePrice` | `number` \| `null` | "Before" price, for a struck-through comparison. Null when not on sale. |
| <a id="price-5" /> `price` | `number` \| `null` | - |

***

### ProductVariantStock

#### Properties

| Property | Type |
| - | - |
| <a id="preorder" /> `preorder` | `boolean` \| `null` |
| <a id="stock-1" /> `stock` | `number` \| `null` |

***

### PurchaseEvent

The order a thank-you page reports, in major units.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="affiliation" /> `affiliation?` | `string` | GA4 `affiliation` — the store the order was placed with. |
| <a id="currency-16" /> `currency` | `string` | - |
| <a id="items-2" /> `items` | [`CommerceItem`](#commerceitem)\[] | - |
| <a id="shipping" /> `shipping?` | `number` | - |
| <a id="tax" /> `tax?` | `number` | - |
| <a id="transaction_id" /> `transaction_id` | `string` | The order number. |
| <a id="value-3" /> `value` | `number` | - |

***

### RelatedProducts

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="mode-4" /> `mode` | [`RelatedProductsMode`](#relatedproductsmode-1) | - |
| <a id="products-1" /> `products` | [`RelatedProductSummary`](#relatedproductsummary)\[] | Visible related products, never the product itself. `specific`: at most 12, in the merchant's order. `category`: at most 8, newest first, without sold-out products when the shop hides them. Always empty for `none`. |

***

### RelatedProductSummary

A related product, reduced to what a product card needs.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="id-12" /> `id` | `string` | Prefixed id (`prod_123`) — pass it to `products.get` or `cart.addItem`. |
| <a id="image-1" /> `image` | [`ProductImage`](#productimage) \| `null` | The product's first image, or null when it has none. |
| <a id="name-7" /> `name` | `string` \| `null` | - |
| <a id="price-6" /> `price` | [`ProductVariantPrice`](#productvariantprice-1) | The product's own price, priced like the product list (automatic discounts and `storefrontId` campaign prices included). |
| <a id="slug-3" /> `slug` | `string` \| `null` | - |

***

### RequestOptions

#### Extended by

* [`ProductListParams`](#productlistparams)
* [`ProductSearchParams`](#productsearchparams)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="signal-3" /> `signal?` | `AbortSignal` | Abort the underlying fetch (component unmount, route change). |

***

### ResolvedShopkitConfig

Config with every default applied and the key already parsed.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="apiurl-1" /> `apiUrl` | `string` | - |
| <a id="cartttlseconds" /> `cartTtlSeconds` | `number` | - |
| <a id="checkouturl-1" /> `checkoutUrl` | `string` | - |
| <a id="checkouturlconfigured" /> `checkoutUrlConfigured` | `boolean` | Whether `checkoutUrl` was set by the caller rather than defaulted. Decides whether a locally built checkout-v2 URL beats the platform's — see [ShopkitConfig.checkoutUrl](#checkouturl-2). |
| <a id="consent-3" /> `consent` | `object` | Consent, defaults applied. `enabled` is false only for `consent: false`. |
| `consent.cookieName` | `string` | - |
| `consent.enabled` | `boolean` | - |
| `consent.revision` | `number` | - |
| <a id="cookieattributes-1" /> `cookieAttributes` | [`CookieAttributes`](#cookieattributes) | - |
| <a id="cookies-1" /> `cookies?` | [`CookieAccessor`](#cookieaccessor) | - |
| <a id="currency-17" /> `currency` | `string` \| `null` | The configured default currency, upper-cased; null for the shop's own. |
| <a id="fetch" /> `fetch` | (`input`, `init?`) => `Promise`\<`Response`> | - |
| <a id="headers" /> `headers` | `Record`\<`string`, `string`> | - |
| <a id="imagebaseurl-1" /> `imageBaseUrl` | `string` \| `null` | - |
| <a id="maxretries" /> `maxRetries` | `number` | - |
| <a id="onunauthorized" /> `onUnauthorized?` | () => `string` \| `Promise`\<`string` \| `null`> \| `null` | - |
| <a id="publishablekey" /> `publishableKey` | `string` | - |
| <a id="scopes-1" /> `scopes` | readonly `string`\[] \| `null` \| `undefined` | `null` = pre-flight checking disabled; `undefined` = use the default set. |
| <a id="shopprefix-2" /> `shopPrefix` | `string` | The shop's storage prefix, decoded from the key. Used in CDN image paths; NOT the numeric id the hosted checkout URL needs (that is `Cart.storeId`). |
| <a id="storage-2" /> `storage?` | [`StoragePreference`](#storagepreference) | - |
| <a id="storagekeyprefix" /> `storageKeyPrefix` | `string` | - |
| <a id="storefront-1" /> `storefront` | [`StorefrontBinding`](#storefrontbinding) \| `null` | The campaign storefront this client sells into, surface defaulted; null when none. |
| <a id="timeoutms" /> `timeoutMs` | `number` | - |

***

### ResumeCheckoutInput

Presentation and default-behaviour options, shared by `mount` and `resume`.

#### Extends

* [`EmbedRenderOptions`](#embedrenderoptions)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="confirmation-5" /> `confirmation?` | `"redirect"` \| `"inline"` | What happens when the order completes. - `"redirect"` (default) — leave the page for the confirmed order, at exactly the URL the redirect checkout would have sent the shopper to (`<successUrl origin>/success/<orderId>?hash=…`), so one thank-you page serves both paths. - `"inline"` — stay on the page and let the frame render its own confirmation. | [`EmbedRenderOptions`](#embedrenderoptions).[`confirmation`](#confirmation-3) |
| <a id="fallbackredirect-3" /> `fallbackRedirect?` | `boolean` | Navigate to the hosted checkout when there is nothing to frame, and when the frame that was built never answers. On by default — a shopper must never be left looking at an empty box because a shop is configured differently than the page assumed, or because this page is not the origin the session was created for. Turn it off to render your own message from the `fallback` event; its `url` is where the shopper should go. | [`EmbedRenderOptions`](#embedrenderoptions).[`fallbackRedirect`](#fallbackredirect-1) |
| <a id="handlenavigation-3" /> `handleNavigation?` | `boolean` | Perform the contract's default navigations (`navigate`, `redirect`, `complete`). Off means the handle only reports; YOU must then perform the redirect break-out yourself or redirect payment methods will not work. Defaults to on. | [`EmbedRenderOptions`](#embedrenderoptions).[`handleNavigation`](#handlenavigation-1) |
| <a id="handshaketimeoutms-3" /> `handshakeTimeoutMs?` | `number` | Override the 15s handshake deadline. For tests, chiefly. | [`EmbedRenderOptions`](#embedrenderoptions).[`handshakeTimeoutMs`](#handshaketimeoutms-1) |
| <a id="language-5" /> `language?` | `string` | - | - |
| <a id="minheight-3" /> `minHeight?` | `number` | Height of the frame before the first `height` message, and the floor it never drops below. Set it to roughly the height of your checkout so the page does not jump on first paint. | [`EmbedRenderOptions`](#embedrenderoptions).[`minHeight`](#minheight-1) |
| <a id="storeid-4" /> `storeId?` | `string` \| `number` \| `null` | The shop's numeric id. The return leg carries it as `qb_checkout_shop` next to the session id (`readReturnedCheckout().storeId`); pass it and resume needs no storage at all. When omitted the id remembered with the checkout session, or off the last cart, is used. | - |
| <a id="successurl-5" /> `successUrl?` | `string` | Where a completed order lands, as on [MountCheckoutInput](#mountcheckoutinput). | - |
| <a id="title-6" /> `title?` | `string` | The iframe's accessible name. Defaults to `"Checkout"`. | [`EmbedRenderOptions`](#embedrenderoptions).[`title`](#title-2) |

***

### ReturnedCheckout

What the checkout's return route put on this page's URL.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="sessionid-6" /> `sessionId` | `string` | The session to resume. |
| <a id="storeid-5" /> `storeId` | `string` \| `null` | The shop's numeric id, or null when the URL did not carry a usable one — a checkout older than the parameter, or a value that is not a number. Pass it to `checkout.resume()` as `storeId`; with null, resume falls back to the id remembered in storage. |

***

### ScopeGuard

#### Properties

| Property | Modifier | Type | Description |
| - | - | - | - |
| <a id="declared-1" /> `declared` | `readonly` | readonly `string`\[] | The scopes this client was told it has. |
| <a id="enforced" /> `enforced` | `readonly` | `boolean` | Whether local pre-flight checking is on at all. |

#### Methods

##### assert()

```ts theme={null}
assert(scope, operation): void;
```

Throws [ShopkitScopeError](#shopkitscopeerror) when `scope` is not covered.

###### Parameters

| Parameter | Type |
| - | - |
| `scope` | \| `"products:read"` \| `"cart:read"` \| `"cart:write"` \| `"checkout:read"` \| `"checkout:write"` \| `"storefront:read"` |
| `operation` | `string` |

###### Returns

`void`

##### has()

```ts theme={null}
has(scope): boolean;
```

###### Parameters

| Parameter | Type |
| - | - |
| `scope` | \| `"products:read"` \| `"cart:read"` \| `"cart:write"` \| `"checkout:read"` \| `"checkout:write"` \| `"storefront:read"` |

###### Returns

`boolean`

***

### SeoBreadcrumb

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="name-8" /> `name` | `string` | - |
| <a id="url-4" /> `url?` | `string` | Absolute URL, or a path resolved against `baseUrl`. |

***

### SeoDefaults

Site-wide values every page shares. Supply once (via `SeoProvider` or as an
argument) so a page only has to name what is specific to it.

#### Extended by

* [`SeoProps`](/kit/api/react#seoprops)
* [`SeoProviderProps`](/kit/api/react#seoproviderprops)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="baseurl-2" /> `baseUrl?` | `string` | Absolute origin of the storefront, e.g. `https://myshop.com`. Required for canonical URLs and for turning a relative image path into an absolute one — crawlers reject relative values in both places. |
| <a id="categorypath-1" /> `categoryPath?` | `string` | The same for a category — `"/categories/{slug}"`. |
| <a id="currency-18" /> `currency?` | `string` | ISO 4217 code for prices in structured data. A product's own `product.currency` wins; this covers a product from a platform older than that field. With neither, no price is emitted. |
| <a id="defaultimage" /> `defaultImage?` | `string` \| [`SeoImage`](#seoimage) | Fallback image for pages with none of their own. |
| <a id="locale-1" /> `locale?` | `string` | BCP 47 locale for OpenGraph (`sv_SE`). Derived from the shop when absent. |
| <a id="organization" /> `organization?` | `boolean` | Emit `Organization` structured data alongside the page's own. |
| <a id="productpath" /> `productPath?` | `string` | Where a product lives, as a template — `"/products/{slug}"`. Lets `<SEO />` build its own canonical from the product in context, so a page inside `<ProductProvider>` needs no props at all. `{slug}` and `{id}` are substituted; a product with no slug falls back to `{id}`. A template rather than a function because these defaults cross a client boundary and get compared by value — a function would neither serialize nor memoize. |
| <a id="shop-1" /> `shop?` | [`Shop`](#shop-2) \| `null` | The shop, for site name, logo, locale and Organization structured data. |
| <a id="sitename" /> `siteName?` | `string` | Shown after the page title. Defaults to the shop's name when a shop is given. |
| <a id="titletemplate" /> `titleTemplate?` | `string` | How to compose the final title. `%s` is the page title. Defaults to `"%s · {siteName}"`, and to the bare title with no site name. |
| <a id="twittercreator" /> `twitterCreator?` | `string` | - |
| <a id="twittersite" /> `twitterSite?` | `string` | `@handle` of the site, for Twitter cards. |

***

### SeoImage

One image, as OpenGraph and schema.org want it.

#### Properties

| Property | Type |
| - | - |
| <a id="alt" /> `alt?` | `string` |
| <a id="height-5" /> `height?` | `number` |
| <a id="url-5" /> `url` | `string` |
| <a id="width-4" /> `width?` | `number` |

***

### SeoInput

What the page is about. Exactly one of these shapes is used.

#### Extended by

* [`SeoProps`](/kit/api/react#seoprops)

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="breadcrumbs" /> `breadcrumbs?` | [`SeoBreadcrumb`](#seobreadcrumb)\[] | Trail for BreadcrumbList when the page builds its own. |
| <a id="category-1" /> `category?` | [`Category`](#category) \| `null` | A category page — emits BreadcrumbList structured data. |
| <a id="description-4" /> `description?` | `string` | - |
| <a id="images-2" /> `images?` | (`string` \| [`SeoImage`](#seoimage))\[] | - |
| <a id="jsonld" /> `jsonLd?` | `Record`\<`string`, `unknown`>\[] | Extra structured data to emit as-is. |
| <a id="meta" /> `meta?` | `object`\[] | - |
| <a id="nofollow" /> `noFollow?` | `boolean` | - |
| <a id="noindex" /> `noIndex?` | `boolean` | Keep the page out of the index — a search results or filter page. |
| <a id="product-3" /> `product?` | [`Product`](#product-1) \| `null` | A product page — emits Product structured data with offers. |
| <a id="title-7" /> `title?` | `string` | Overrides whatever the product/category/shop would have produced. |
| <a id="url-6" /> `url?` | `string` | Absolute URL, or a path resolved against `baseUrl`. |

***

### SeoOpenGraph

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="availability" /> `availability?` | [`SchemaAvailability`](#schemaavailability) | - |
| <a id="description-5" /> `description?` | `string` | - |
| <a id="images-3" /> `images` | [`SeoImage`](#seoimage)\[] | - |
| <a id="locale-2" /> `locale?` | `string` | - |
| <a id="price-7" /> `price?` | `object` | Present only on `type: "product"`, and only when a currency is known. |
| `price.amount` | `string` | - |
| `price.currency` | `string` | - |
| <a id="sitename-1" /> `siteName?` | `string` | - |
| <a id="title-8" /> `title` | `string` | - |
| <a id="type-1" /> `type` | `"article"` \| `"website"` \| `"product"` | `website` for the shop and category pages, `product` for a product. |
| <a id="url-7" /> `url?` | `string` | - |

***

### SeoRobots

#### Properties

| Property | Type |
| - | - |
| <a id="follow" /> `follow` | `boolean` |
| <a id="index-1" /> `index` | `boolean` |

***

### SeoTags

Everything a page needs in `<head>`, as data.

Deliberately a plain object rather than markup: Next's App Router wants a
`Metadata` export, Astro wants tags in its own layout, and a React 19 app can
render them anywhere. One builder, three consumers.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="canonical" /> `canonical?` | `string` | Absolute canonical URL. Omitted when no `url`/`baseUrl` was available. |
| <a id="description-6" /> `description?` | `string` | - |
| <a id="jsonld-1" /> `jsonLd` | `Record`\<`string`, `unknown`>\[] | schema.org objects for `<script type="application/ld+json">`. This is where the actual rich-result value sits — price, availability, breadcrumbs — not in the meta tags. |
| <a id="meta-1" /> `meta` | `object`\[] | Extra `<meta name=… content=…>` pairs, e.g. a verification token. |
| <a id="opengraph-1" /> `openGraph` | [`SeoOpenGraph`](#seoopengraph) | - |
| <a id="robots-1" /> `robots` | [`SeoRobots`](#seorobots) | - |
| <a id="title-9" /> `title` | `string` | - |
| <a id="twitter-1" /> `twitter` | [`SeoTwitter`](#seotwitter) | - |

***

### SeoTwitter

#### Properties

| Property | Type |
| - | - |
| <a id="card" /> `card` | `"summary"` \| `"summary_large_image"` |
| <a id="creator" /> `creator?` | `string` |
| <a id="description-7" /> `description?` | `string` |
| <a id="images-4" /> `images` | `string`\[] |
| <a id="site" /> `site?` | `string` |
| <a id="title-10" /> `title` | `string` |

***

### SessionConfirmation

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="legacysuccessurl" /> `legacySuccessUrl?` | `string` | The platform's own success-page URL. A headless storefront normally ignores this and renders its own page — it is exposed because the hosted checkout uses it, and because it is the one place the order number appears in a URL. |
| <a id="ordernumber-1" /> `orderNumber?` | `number` | - |
| <a id="retryafterms" /> `retryAfterMs?` | `number` | Server hint for the next poll delay, in ms. |
| <a id="status-5" /> `status` | [`ConfirmationStatus`](#confirmationstatus) | - |
| <a id="successmode-4" /> `successMode?` | `"redirect"` \| `"inline"` | How the session ends, as the platform echoes it. Missing means `"redirect"`. In `"inline"` mode the checkout shows the confirmation itself, so `legacySuccessUrl` is never present and this poll is the storefront's only signal that the order exists. |

***

### SessionEmbed

Where a checkout session may be embedded.

The session is the trust anchor for framing: the party that creates it — the
same party that already chooses `successUrl` — declares which single origin
may put the checkout in an iframe, and the checkout emits exactly that origin
as its `Content-Security-Policy: frame-ancestors`. A session created without
this cannot be framed at all, by anyone.

`checkout.mount()` fills both fields in for you from the current page. Pass
them yourself only when the page that CREATES the session is not the page
that will frame it — a server-side create for a storefront route, say.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="origin-2" /> `origin` | `string` | Exactly one origin (`scheme://host[:port]`, no path, no query, no wildcard). `https` only, except `http` on loopback. |
| <a id="returnurl-1" /> `returnUrl` | `string` | Where a redirect payment method (Klarna, Swish, iDEAL, full-page 3-D Secure) returns the shopper to. Must be a well-formed URL whose origin equals [origin](#origin-2); the checkout appends `?qb_checkout_session=<id>` to it, which is what lets the embed resume where it left off. |

***

### SessionPrefill

Everything seeded at creation. A value the field rejects lands as
`status: "invalid"` on that field — it never fails the create call, so a
stale saved address can't block a shopper from checking out.

#### Properties

| Property | Type |
| - | - |
| <a id="billing_address" /> `billing_address?` | [`SessionPrefillAddress`](#sessionprefilladdress) |
| <a id="customer" /> `customer?` | [`SessionPrefillCustomer`](#sessionprefillcustomer-1) |
| <a id="shipping_address" /> `shipping_address?` | [`SessionPrefillAddress`](#sessionprefilladdress) |

***

### SessionPrefillAddress

#### Properties

| Property | Type |
| - | - |
| <a id="city" /> `city?` | `string` |
| <a id="company" /> `company?` | `string` |
| <a id="companynumber" /> `companyNumber?` | `string` |
| <a id="country" /> `country?` | `string` |
| <a id="firstname" /> `firstName?` | `string` |
| <a id="lastname" /> `lastName?` | `string` |
| <a id="line1" /> `line1?` | `string` |
| <a id="line2" /> `line2?` | `string` |
| <a id="phone" /> `phone?` | `string` |
| <a id="postalcode" /> `postalCode?` | `string` |

***

### SessionPrefillCustomer

Known shopper identity, seeded into the session so the checkout prefills.

#### Properties

| Property | Type |
| - | - |
| <a id="customertype" /> `customerType?` | [`CustomerType`](#customertype-1) |
| <a id="email" /> `email` | `string` |
| <a id="postalcode-1" /> `postalCode` | `string` |

***

### Shop

Shop-level presentation data a storefront needs before it can render
anything: the name, the logo, the default language, the terms link.

Served by the checkout's shop endpoint, which is the only shop surface a
publishable key can reach — shop *settings* are a merchant credential's
business, not a storefront's.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="brand-1" /> `brand` | [`ShopBrand`](#shopbrand-1) | - |
| <a id="currencies-1" /> `currencies?` | [`ShopCurrency`](#shopcurrency-1)\[] | Every currency a shopper may browse in, the base currency first. Only the base entry when the shop's currency converter is off. Absent from a platform older than the field — read it as "the base currency only". |
| <a id="currency-19" /> `currency?` | `string` \| `null` | The shop's own (base) currency, upper-case ISO 4217 — what every price is in when no other currency is chosen. Absent from a platform older than the field. |
| <a id="language-6" /> `language?` | `string` \| `null` | Default language as a raw code ("sv"). |
| <a id="messageenabled" /> `messageEnabled?` | `boolean` | Whether shoppers may leave a message on the order. |
| <a id="name-9" /> `name` | `string` | - |
| <a id="newsletterenabled" /> `newsletterEnabled?` | `boolean` | Whether the merchant runs a newsletter (gates the opt-in checkbox). |
| <a id="storeurl" /> `storeUrl?` | `string` \| `null` | The platform-issued storefront URL. |
| <a id="termsurl" /> `termsUrl?` | `string` \| `null` | Terms & conditions page, for the checkout consent copy. |
| <a id="tracking" /> `tracking?` | `ShopTracking` \| `null` | The merchant's tracking configuration, as the hosted checkout reads it. What `destinationsFromShop()` turns into GA4 / GTM / Meta destinations. Absent from platforms older than the field. |

***

### ShopBrand

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="color" /> `color` | [`ShopBrandColor`](#shopbrandcolor-1) | - |
| <a id="description-8" /> `description?` | `string` \| `null` | The merchant's short description of the shop. Serves as the homepage meta description when the page supplies none of its own. |
| <a id="favicon" /> `favicon?` | `string` \| `null` | - |
| <a id="logo" /> `logo` | `string` \| `null` | - |

***

### ShopBrandColor

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="primary" /> `primary` | `string` \| `null` | Hex, e.g. `#1a73e8`. Null when the merchant never set one. |

***

### ShopCurrency

One currency a shop offers.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="code-1" /> `code` | `string` | Upper-case ISO 4217. |
| <a id="mode-5" /> `mode` | [`ShopCurrencyMode`](#shopcurrencymode-1) | - |
| <a id="rate-2" /> `rate` | `number` | Units of this currency per 1 unit of the shop's base currency. |

***

### ShopkitConfig

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="apiurl-2" /> `apiUrl?` | `string` | Commerce API origin. Defaults to [DEFAULT\_API\_URL](#default_api_url); point it at your preview/rig gateway for local development. A trailing `/v2` is tolerated. |
| <a id="cartttlseconds-1" /> `cartTtlSeconds?` | `number` | How long a remembered cart id lives. Defaults to 30 days. |
| <a id="checkouturl-2" /> `checkoutUrl?` | `string` | Origin of the checkout-v2 host the shopper is handed off to. Defaults to [DEFAULT\_CHECKOUT\_URL](#default_checkout_url). Setting this explicitly makes it WIN over the URL the platform returns for a checkout-v2 handoff — which is what a rig, a preview environment or a checkout proxied onto the merchant's own domain needs. It has no effect on a shop running the legacy checkout: that URL is only ever the platform's, because it points at an order the platform just created. |
| <a id="consent-4" /> `consent?` | `boolean` \| [`ShopkitConsentConfig`](#shopkitconsentconfig) | Cookie consent. On by default: `<ShopkitProvider>` and the elements show a consent banner and load the shop's own GA4 / GTM / Meta pixel once the shopper agrees, and `checkout.start()` on a server client with cookie access forwards the decision to the hosted checkout. An object sets the policy `revision` (and the cookie name) for all of them at once. `false` turns the whole thing off: no banner, no consent store, no analytics, and nothing appended to checkout URLs. An explicit `consent` prop on `<ShopkitProvider>` wins over this. |
| <a id="cookieattributes-2" /> `cookieAttributes?` | [`CookieAttributes`](#cookieattributes) | Attributes for cookies shopkit writes. |
| <a id="cookies-2" /> `cookies?` | [`CookieAccessor`](#cookieaccessor) | Server-side cookie access. Supplying this is what makes an RSC, a route handler or a server function see the same cart as the browser. |
| <a id="currency-20" /> `currency?` | `string` \| `null` | The currency to browse in — `"EUR"`, any case — until the shopper picks one with `client.setCurrency()`. Every product read and cart request then carries `?currency=EUR` and every checkout carries it in its body; the shop decides what it means. A currency the shop offers in `"display"` mode is shown converted and charged in the shop's currency at checkout, one offered in `"charge"` mode is priced and charged in it, and one it does not offer falls back to the shop's own currency without an error. Responses state the currency their amounts are in (`product.currency`, `cart.currency`), so format with those. A choice made with `setCurrency()` is remembered (under `${storageKeyPrefix}_currency`) and wins over this default on later visits. Omitted or `null`: the shop's own currency. A value that is not a three-letter code throws a `ShopkitConfigError`. |
| <a id="fetch-1" /> `fetch?` | (`input`, `init?`) => `Promise`\<`Response`> | Injected fetch — for tests, or for a framework's instrumented fetch. |
| <a id="headers-1" /> `headers?` | `Record`\<`string`, `string`> | Extra headers on every request (tracing, a custom user agent). |
| <a id="imagebaseurl-2" /> `imageBaseUrl?` | `string` | Fallback base URL for the shop's image storage. Normally unnecessary: every product image arrives with an absolute `url` already. Set this only to cover a shop on an older api-core that still sends the bare filename, or to route images through your own CDN. It is the shop-scoped base *without* the `products/` segment, e.g. `https://cdn.quickbutik.com/images/<shopPrefix>` — the prefix being the one in the key, `shopkit.shopPrefix`. |
| <a id="maxretries-1" /> `maxRetries?` | `number` | Retries for transient failures (network error, 429, 5xx) on idempotent requests only. Defaults to 2. |
| <a id="onunauthorized-1" /> `onUnauthorized?` | () => `string` \| `Promise`\<`string` \| `null`> \| `null` | Called when a request comes back 401 — the key was rotated or revoked. Return a fresh key to adopt it and retry once; return null to let the 401 surface. |
| <a id="publishablekey-1" /> `publishableKey` | `string` | The shop's publishable key (`qb_pk_…`). Safe to ship to a browser: it is scope-limited, shop-scoped and grants no merchant data. A `qb_pat_…` personal access token is NOT interchangeable and is rejected. |
| <a id="scopes-2" /> `scopes?` | \| readonly ( \| `"products:read"` \| `"cart:read"` \| `"cart:write"` \| `"checkout:read"` \| `"checkout:write"` \| `"storefront:read"`)\[] \| readonly `string`\[] \| `null` | The scopes the key carries. Used for a local pre-flight check that turns an opaque 403 into a named error before the request leaves the process. Defaults to the standard storefront set. Pass `null` to disable the check when the key's scopes are decided elsewhere. |
| <a id="storage-3" /> `storage?` | [`StoragePreference`](#storagepreference) | How to persist the cart / session ids. See [StoragePreference](#storagepreference). |
| <a id="storagekeyprefix-1" /> `storageKeyPrefix?` | `string` | Prefix for stored keys. Change it to run two shops in one browser. Defaults to `"qb"`, or `qb_<storefront id lower-cased>` when a [storefront](#storefront-2) is bound. |
| <a id="storefront-2" /> `storefront?` | [`ShopkitStorefrontConfig`](#shopkitstorefrontconfig) \| `null` | Sell into one campaign storefront (a Säljplats in the Quickbutik admin) with this whole client. Every cart it creates is bound to the campaign — priced with the campaign's overrides on every read — and every checkout it starts carries the campaign's id and `surface` for the live-guard, the quantity limits and order attribution. `surface` defaults to `"shopkit"`. This is the binding the remembering calls (`cart.ensure()`, `cart.add()`, `checkout.start()` without a `cartId`) work against; a stateless call (`cart.create()`, a checkout given an explicit `cartId`) may name any campaign. Also changes the default [storageKeyPrefix](#storagekeyprefix-1) to `qb_<id lower-cased>` (`qb_sf_01j…`), so the campaign's cart is remembered under its own cookie and never mixes with the shop's ordinary `qb_cart_id` cart or another campaign's — those carts are priced differently and must stay apart. Adding this to an EXISTING integration therefore stops it reading carts and sessions remembered under the old prefix; pass `storageKeyPrefix: "qb"` to keep them. The id is checked here and a malformed one throws a `ShopkitConfigError`; whether the campaign exists and is open is only known at checkout — see `storefrontRefusal()`. `null` means no binding, for a conditional config. |
| <a id="storefrontid-8" /> `storefrontId?` | `string` \| `null` | Shorthand for `storefront: { id }` — the campaign storefront (`sf_…`) this client sells into, with the surface defaulted to `"shopkit"`. Everything [storefront](#storefront-2) says applies: bound carts, campaign-priced product reads, the checkout attribution and the separate storage prefix. It exists because the id is very often the only thing a page has — a `storefront-id` attribute, a `data-storefront-id` on the script tag, a `<ShopkitProvider storefrontId>` prop, a route param — and wrapping it in an object at every one of those places is noise. Both may be set as long as they name the same campaign (case-insensitive, as the platform compares them); the surface then comes from `storefront`. Two different ids throw a `ShopkitConfigError` rather than silently choosing one. `null` and `""` mean "not set", for a conditional config. |
| <a id="timeoutms-1" /> `timeoutMs?` | `number` | Per-request timeout in ms. Defaults to 15000. 0 disables it. |

***

### ShopkitConsentConfig

The consent settings every part of the kit has to agree on, kept on the
client config so the server client, the browser provider and the checkout
handoff all read them from the same object.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="cookiename-4" /> `cookieName?` | `string` | The cookie the decision lives in. Defaults to `qb_consent`. |
| <a id="revision-7" /> `revision?` | `number` | The cookie-policy revision. Bump it when the policy changes materially and every shopper is asked again: a decision stored under an older revision reads as undecided. Defaults to 1. |

***

### ShopkitRuntimeInfo

What the client resolved about its environment — handy when debugging.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="environment" /> `environment` | [`ShopkitRuntime`](#shopkitruntime) | `"browser"` or `"server"`. |
| <a id="persistent" /> `persistent` | `boolean` | True when ids can actually be remembered across requests. |
| <a id="storage-4" /> `storage` | `string` | Which storage adapter won: `cookie`, `localStorage`, `memory`, custom. |

***

### ShopkitStorefrontConfig

`ShopkitConfig.storefront` — bind a whole client to one campaign.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="id-13" /> `id` | `string` | The campaign storefront id, `sf_…`. |
| <a id="surface-5" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` | Defaults to `"shopkit"`. |

***

### StartCheckoutInput

Input to `checkout.start()`: a `CreateSessionInput` whose cart is optional.
The `successUrl` rule is the same — required unless `successMode: "inline"`.

#### Extends

* `Omit`\<[`CreateSessionInput`](#createsessioninput), `"cartId"`>

#### Extended by

* [`MountCheckoutInput`](#mountcheckoutinput)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="backurl-4" /> `backUrl?` | `string` | Where the hosted checkout's "back to shop" / "continue shopping" links point — the header logo and back arrow, the empty-cart screen and the order confirmation. Same URL rule as `successUrl`: any `https` host, `http` only for `localhost`/loopback. When omitted the checkout falls back to the shop's storefront URL, then to the origin of `successUrl` — which for a headless storefront is usually the right page anyway. Set it when your storefront lives on a path or you want a specific landing page. In `inline` success mode it is the ONLY way home from the confirmation, so a headless or campaign storefront should set it to the page the shopper came from. | [`CreateSessionInput`](#createsessioninput).[`backUrl`](#backurl-2) |
| <a id="cancelurl-4" /> `cancelUrl?` | `string` | Where "back to cart" goes. | [`CreateSessionInput`](#createsessioninput).[`cancelUrl`](#cancelurl-2) |
| <a id="cartid-6" /> `cartId?` | `string` | Defaults to the remembered cart (created if this visitor has none). | - |
| <a id="currency-21" /> `currency?` | `string` \| `null` | The currency the shopper is browsing in. Defaults to the client's currency (`client.currency`); pass `null` to send none. The shop decides what happens with it, exactly as for product and cart reads: a `"display"` currency leaves the session in the shop's currency and the response carries it as [CheckoutSession.displayCurrency](#displaycurrency); a `"charge"` currency prices and charges the session in it; anything else has no effect. Fixed when the session is created — a session for the same cart in a different currency is a new session. checkout-v2 only. | [`CreateSessionInput`](#createsessioninput).[`currency`](#currency-8) |
| <a id="embed-4" /> `embed?` | [`SessionEmbed`](#sessionembed) | Declare where this session may be framed. Required for an embedded checkout and ignored by the redirect one — see [SessionEmbed](#sessionembed). | [`CreateSessionInput`](#createsessioninput).[`embed`](#embed-2) |
| <a id="language-7" /> `language?` | `string` | Checkout UI language ("sv", "en", "en-US"). Validation is lenient — a code the checkout cannot translate falls back to the shop default rather than failing. | [`CreateSessionInput`](#createsessioninput).[`language`](#language-2) |
| <a id="prefill-2" /> `prefill?` | [`SessionPrefill`](#sessionprefill) | - | [`CreateSessionInput`](#createsessioninput).[`prefill`](#prefill) |
| <a id="storefrontid-9" /> `storefrontId?` | `string` | The campaign storefront this checkout is started from — `sf_…`. Binds the session to the campaign: its prices apply, it must be live, its quantity limits are enforced, and its id goes on the order. Falls back to the client's `storefront` binding; when neither is set the field is not sent, and a cart already bound to a campaign passes its binding on server-side. With an explicit `cartId` any campaign may be named. Without one (`checkout.start()` / `buyNow()` on the remembered cart) it must match the client's binding — see `StorefrontAttribution`. | [`CreateSessionInput`](#createsessioninput).[`storefrontId`](#storefrontid-3) |
| <a id="successmode-5" /> `successMode?` | `"redirect"` \| `"inline"` | `"redirect"` (the default when omitted) or `"inline"`. See [SuccessMode](#successmode-7). | [`CreateSessionInput`](#createsessioninput).[`successMode`](#successmode-2) |
| <a id="successurl-6" /> `successUrl?` | `string` | Where the shopper lands after paying. Required unless `successMode: "inline"`, where the checkout never redirects. Any `https` URL is accepted — the host needs no registration and no relationship to the shop's platform URL. `http` is accepted only for `localhost` and loopback addresses; a `javascript:` URL, or any other scheme, is rejected with 400. Only the ORIGIN is used for the post-payment redirect: the hosted checkout sends the shopper to `<origin>/success/<orderNumber>?hash=…&t=…`, so mount a route there (see `parseReturnUrl`). | [`CreateSessionInput`](#createsessioninput).[`successUrl`](#successurl-2) |
| <a id="surface-6" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` | Where the campaign was presented to this shopper. Only sent together with a `storefrontId`; defaults to the client's surface, then `"shopkit"`. | [`CreateSessionInput`](#createsessioninput).[`surface`](#surface-3) |
| <a id="theme-3" /> `theme?` | `"light"` \| `"dark"` | Which theme the hosted checkout is painted in for this shopper. Set it when your own pages are dark, so the checkout does not flash white on the way in. Omitting it is not the same as passing `"light"`: omit it and the checkout follows the theme the merchant chose for their shop, pass `"light"` and this checkout stays light whatever the merchant chose. Fixed when the session is created and not changeable afterwards, so decide it with the rest of the page's theme. If you omit it, the merchant's choice applies live — a shopper with the checkout already open sees it change when the merchant changes it. | [`CreateSessionInput`](#createsessioninput).[`theme`](#theme-1) |

***

### StorageAdapter

Where shopkit keeps the ids it must remember between requests: the cart id,
the checkout session id and the cart's numeric store id.

Every method may return a promise. That is not decoration — Next.js 15's
`cookies()` is async, so a synchronous-only contract would lock the most
common server integration out.

#### Properties

| Property | Modifier | Type | Description |
| - | - | - | - |
| <a id="kind" /> `kind` | `readonly` | `string` | Short name used in error messages and `client.runtime.storage`. |

#### Methods

##### get()

```ts theme={null}
get(key): MaybePromise<string | null>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `key` | `string` |

###### Returns

[`MaybePromise`](#maybepromise)\<`string` | `null`>

##### remove()

```ts theme={null}
remove(key): MaybePromise<void>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `key` | `string` |

###### Returns

[`MaybePromise`](#maybepromise)\<`void`>

##### set()

```ts theme={null}
set(
   key, 
   value, 
options?): MaybePromise<void>;
```

###### Parameters

| Parameter | Type |
| - | - |
| `key` | `string` |
| `value` | `string` |
| `options?` | [`StorageSetOptions`](#storagesetoptions) |

###### Returns

[`MaybePromise`](#maybepromise)\<`void`>

***

### StorageSetOptions

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="ttlseconds" /> `ttlSeconds?` | `number` | Lifetime in seconds. Adapters that cannot expire values ignore it. |

***

### StorefrontAttribution

Per-call attribution.

Accepted freely by the STATELESS calls — `cart.create()`, and
`checkout.createSession()` / `checkout.start()` given an explicit `cartId` —
where the caller owns which cart is involved. On the REMEMBERING calls
(`cart.ensure()`, `cart.add()`, `start()` / `buyNow()` without a `cartId`) a
`storefrontId` must match the client's binding, because those calls reuse
the cart the client remembers, and that cart was created for one campaign:
a different id would be refused by the platform (`storefront_mismatch`), and
on an unbound client it would leave a campaign-priced cart remembered as the
shop's ordinary one for thirty days. See assertRememberingCallBinding.

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="storefrontid-10" /> `storefrontId?` | `string` | The campaign storefront to sell into. |
| <a id="surface-7" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` | Where the campaign was presented. Ignored unless a `storefrontId` resolves from this call or the client — a placement without a campaign is nothing. |

***

### StorefrontBinding

The binding with the default applied — what `ShopkitClient.storefront` is.

#### Properties

| Property | Type |
| - | - |
| <a id="id-14" /> `id` | `string` |
| <a id="surface-8" /> `surface` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` |

***

### TransportRequest

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="body-1" /> `body?` | `unknown` | - |
| <a id="errormessage" /> `errorMessage?` | `string` | Message used when the server gives us nothing better. |
| <a id="method" /> `method` | `"GET"` \| `"POST"` \| `"PATCH"` \| `"PUT"` \| `"DELETE"` | - |
| <a id="path-3" /> `path` | `string` | Path relative to the API version, e.g. `/checkout/sessions`. |
| <a id="query" /> `query?` | `Record`\<`string`, `string` \| `number` \| `boolean` \| `null` \| `undefined`> | - |
| <a id="signal-4" /> `signal?` | `AbortSignal` | - |

***

### V2Handoff

A `v2` handoff: an ordinary checkout session, plus which checkout answered.

#### Extends

* [`CheckoutSession`](#checkoutsession)

#### Properties

| Property | Type | Description | Inherited from |
| - | - | - | - |
| <a id="backurl-5" /> `backUrl` | `string` \| `null` | - | [`CheckoutSession`](#checkoutsession).[`backUrl`](#backurl) |
| <a id="cancelurl-5" /> `cancelUrl` | `string` \| `null` | - | [`CheckoutSession`](#checkoutsession).[`cancelUrl`](#cancelurl) |
| <a id="cartid-7" /> `cartId` | `string` | - | [`CheckoutSession`](#checkoutsession).[`cartId`](#cartid-1) |
| <a id="checkout-2" /> `checkout` | `"v2"` | - | - |
| <a id="data-6" /> `data` | [`SessionDataMap`](#sessiondatamap) | - | [`CheckoutSession`](#checkoutsession).[`data`](#data-2) |
| <a id="displaycurrency-3" /> `displayCurrency?` | [`DisplayCurrency`](#displaycurrency-2) \| `null` | The currency the checkout shows an approximate amount in, or null when none applies (no currency sent, the shop's own currency, or a `"charge"` currency — then `data.pricing.value.currency` is it). Absent on older platforms. | [`CheckoutSession`](#checkoutsession).[`displayCurrency`](#displaycurrency) |
| <a id="embed-5" /> `embed?` | [`SessionEmbed`](#sessionembed) \| `null` | The embed permission recorded at creation, or null when there is none. | [`CheckoutSession`](#checkoutsession).[`embed`](#embed) |
| <a id="fields-3" /> `fields` | [`SessionFieldMap`](#sessionfieldmap) | - | [`CheckoutSession`](#checkoutsession).[`fields`](#fields) |
| <a id="language-8" /> `language` | `string` \| `null` | - | [`CheckoutSession`](#checkoutsession).[`language`](#language) |
| <a id="origin-3" /> `origin` | `string` \| `null` | `"storefront-native"` only for the built-in storefront; null for headless. | [`CheckoutSession`](#checkoutsession).[`origin`](#origin) |
| <a id="sessionid-7" /> `sessionId` | `string` | - | [`CheckoutSession`](#checkoutsession).[`sessionId`](#sessionid-1) |
| <a id="storefrontid-11" /> `storefrontId?` | `string` \| `null` | The campaign storefront the session is bound to, or null. Absent on older platforms. | [`CheckoutSession`](#checkoutsession).[`storefrontId`](#storefrontid-1) |
| <a id="successmode-6" /> `successMode?` | `"redirect"` \| `"inline"` | How this checkout ends, as the platform echoes it. Missing means `"redirect"`. | [`CheckoutSession`](#checkoutsession).[`successMode`](#successmode) |
| <a id="successurl-7" /> `successUrl` | `string` \| `null` | The success URL the session was created with, or null when the session was created in inline mode without a successUrl. | [`CheckoutSession`](#checkoutsession).[`successUrl`](#successurl) |
| <a id="surface-9" /> `surface?` | `"embed"` \| `"link"` \| `"hosted"` \| `"shopkit"` \| `"agentic"` \| `null` | - | [`CheckoutSession`](#checkoutsession).[`surface`](#surface-1) |
| <a id="theme-4" /> `theme` | `"light"` \| `"dark"` | The theme this checkout is painted in — always `"light"` or `"dark"`, never absent. This is the answer, not an echo: it is what you asked for when you asked for something, and the merchant's shop-wide choice when you did not. You cannot tell the two apart from here, and you do not need to — this is what the shopper sees. | [`CheckoutSession`](#checkoutsession).[`theme`](#theme) |

***

### VariantOptionGroupState

#### Properties

| Property | Type |
| - | - |
| <a id="id-15" /> `id` | `number` |
| <a id="name-10" /> `name` | `string` \| `null` |
| <a id="position-3" /> `position` | `number` \| `null` |
| <a id="selectedvalueid" /> `selectedValueId` | `number` \| `null` |
| <a id="values" /> `values` | [`VariantOptionValueState`](#variantoptionvaluestate)\[] |

***

### VariantOptionValueState

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="available-1" /> `available` | `boolean` | Whether a purchasable variant exists for this value **given the choices made in the other groups**. This is what makes a non-rectangular matrix work: if Red exists only in L and XL, then picking Red must report XXL unavailable — while picking XXL first must report Red unavailable. Availability is always evaluated with this value's own group excluded, so a value never invalidates itself. |
| <a id="id-16" /> `id` | `number` | - |
| <a id="name-11" /> `name` | `string` \| `null` | - |
| <a id="optionid-1" /> `optionId` | `number` | The option group this value belongs to ("Color"). |
| <a id="position-4" /> `position` | `number` \| `null` | - |
| <a id="selected" /> `selected` | `boolean` | - |
| <a id="variantids" /> `variantIds` | `number`\[] | Ids of the variants this value still reaches. Useful for stock/price UI. |

***

### VariantPriceState

#### Properties

| Property | Type | Description |
| - | - | - |
| <a id="amount-1" /> `amount` | `number` \| `null` | The selected variant's price. Null until a variant is fully resolved. |
| <a id="compareatamount" /> `compareAtAmount` | `number` \| `null` | "Before" price of the selected variant, for a struck-through comparison. |
| <a id="isrange" /> `isRange` | `boolean` | True when the reachable variants do not all cost the same — the cue for rendering "from 99 kr" instead of a single figure. |
| <a id="max" /> `max` | `number` \| `null` | Highest price among the variants still reachable from the selection. |
| <a id="min" /> `min` | `number` \| `null` | Lowest price among the variants still reachable from the selection. |

## Type Aliases

### AsyncStatus

```ts theme={null}
type AsyncStatus = "idle" | "loading" | "ready" | "error";
```

***

### BuyNowResult

```ts theme={null}
type BuyNowResult = StartCheckoutResult & object;
```

What `checkout.buyNow()` resolves to: the [StartCheckoutResult](#startcheckoutresult) of the
handoff, plus the cart the add produced. `cart` is the state BEFORE the
shopper pays — the same basket the checkout was started from.

#### Type Declaration

| Name | Type | Description |
| - | - | - |
| `cart` | [`Cart`](#cart-1) | The remembered cart after the item was added. |

***

### CartCreateOptions

```ts theme={null}
type CartCreateOptions = RequestOptions & StorefrontAttribution;
```

Options for the calls that may CREATE a cart: the request signal, plus the
campaign storefront to bind the new cart to. On `create()` any campaign may
be named; on `ensure()` / `add()`, which reuse the remembered cart, a
`storefrontId` must match the client's binding — see `StorefrontAttribution`.

***

### CheckoutFlavour

```ts theme={null}
type CheckoutFlavour = "v2" | "legacy";
```

Which checkout a shop actually runs.

* `"v2"` — the current Quickbutik checkout. The handoff is a checkout
  session, and the whole SDK surface applies: `getSession`, patching,
  `parseReturnUrl`, the lot.
* `"legacy"` — the older checkout. The handoff is an unpaid ORDER, not a
  session, and the platform creates it for you. See [LegacyHandoff](#legacyhandoff) for
  what that costs you.

The shop decides this, not the storefront: it is whether checkout-v2 has been
activated for that shop. It can differ between two shops using the same
storefront code, so branch on it rather than assuming.

***

### CheckoutHandoff

```ts theme={null}
type CheckoutHandoff = V2Handoff | LegacyHandoff & object;
```

What `POST /v2/checkout/handoff` answers with, before the URL is resolved.

#### Type Declaration

| Name | Type | Description |
| - | - | - |
| `embedUrl?` | `string` \| `null` | Where to frame the checkout, when the request asked to embed it — `/embed/{storeId}/{sessionId}`, on the checkout's own origin. `null` means there is nothing to frame: no `embed` was supplied, the shop is on the legacy checkout (which is not embeddable and will not be made so), or the platform has no checkout host configured for it. **Absent** means an api-core older than the embedded checkout. `mount()` treats all of those the same way — it falls back to the redirect checkout and emits `fallback` — because none of them is retryable from a storefront. |
| `hostedUrl?` | `string` \| `null` | Where to send the shopper. Three states, and they mean different things: - a URL — use it. - `null` — the platform HAS an opinion and it is "I have no host configured for this shop's checkout". A misconfiguration, not a retryable error, and emphatically not an invitation to guess: guessing would send a preprod or rig shopper to the production checkout, which is the exact failure this endpoint exists to eliminate. `start()` throws. - **absent** — an api-core older than this field, which has no opinion. Only here does `start()` fall back to building the URL locally. |

***

### CommerceEventName

```ts theme={null}
type CommerceEventName = 
  | "page_view"
  | "view_item"
  | "view_item_list"
  | "select_item"
  | "search"
  | "add_to_cart"
  | "remove_from_cart"
  | "view_cart"
  | "begin_checkout"
  | "add_shipping_info"
  | "add_payment_info"
  | "purchase"
  | string & object;
```

The event vocabulary is GA4's, because every destination understands it or
can be mapped from it (the Meta destination translates), because it is
what the hosted checkout already emits (`<Checkout onEvent>`), and because
a GTM container reads it off the dataLayer with no extra configuration.
The string escape hatch is for a storefront's own events.

***

### ConfirmationOutcome

```ts theme={null}
type ConfirmationOutcome = 
  | {
  confirmation: SessionConfirmation;
  kind: "completed";
  orderNumber: number | null;
}
  | {
  confirmation: SessionConfirmation;
  kind: "failed";
}
  | {
  kind: "timeout";
  lastStatus: ConfirmationStatus | null;
}
  | {
  kind: "aborted";
};
```

Terminal outcome of polling. `timeout` is not a failure: order creation is
asynchronous and may still land — render "we're still working on it" rather
than "your payment failed".

***

### ConfirmationStatus

```ts theme={null}
type ConfirmationStatus = "completed" | "processing_payment" | "no_attempt" | "failed";
```

* `completed` — the order exists.
* `processing_payment` — payment authorized, order being created (6–17s).
* `no_attempt` — no payment attempt known for this session yet.
* `failed` — the payment attempt failed terminally.

***

### ConsentCategory

```ts theme={null}
type ConsentCategory = "necessary" | "analytics" | "marketing";
```

The consent model, kept to the three categories the rest of the platform
speaks: the hosted storefront's banner, the hosted checkout's handoff
parameter (`consentCategories=analytics,marketing`) and Google Consent
Mode's signals all map onto exactly these. A fourth category would be a
non-breaking addition to the union, but nothing downstream would read it
today, so there is none.

`necessary` is never a choice: the cart id and checkout session id the kit
stores are strictly necessary and need no consent (see `SessionStore`).

***

### ConsentModeValue

```ts theme={null}
type ConsentModeValue = "granted" | "denied";
```

***

### ConsentStatus

```ts theme={null}
type ConsentStatus = "undecided" | "decided";
```

* `undecided` — no decision stored for the current revision. Everything
  optional is treated as denied until the shopper chooses.
* `decided` — the shopper chose; `analytics` and `marketing` say what.

***

### CookieList

```ts theme={null}
type CookieList = ReadonlyArray<{
  name: string;
  value: string;
}>;
```

Cookies as a list of pairs: what Next's `cookies().getAll()` returns.

***

### CountryCode

```ts theme={null}
type CountryCode = string;
```

ISO 3166-1 alpha-2, uppercase — "SE", "NO", "DK".

***

### CurrencyCode

```ts theme={null}
type CurrencyCode = string;
```

ISO 4217, uppercase — "SEK", "NOK", "EUR".

***

### CustomerType

```ts theme={null}
type CustomerType = "b2c" | "b2b";
```

***

### EmbeddedCheckoutErrorCode

```ts theme={null}
type EmbeddedCheckoutErrorCode = EmbedErrorCode;
```

Error codes an `error` event can carry.

All of them come from the frame today. The host raises none of its own: the
one failure it can notice by itself — a frame that never answers — is not an
error to the shopper but a reason to open the hosted checkout instead, so it
travels as a `fallback` (see [EmbeddedCheckoutFallbackReason](#embeddedcheckoutfallbackreason)).

***

### EmbeddedCheckoutEventType

```ts theme={null}
type EmbeddedCheckoutEventType = keyof EmbeddedCheckoutEventMap;
```

***

### EmbeddedCheckoutFallbackReason

```ts theme={null}
type EmbeddedCheckoutFallbackReason = "legacy" | "no-embed-url" | "no-top-navigation" | "handshake-timeout";
```

Why an embed handle is not actually embedding anything.

The first three are decided before any frame exists; the last one after a
frame was built and stayed silent.

* `legacy` — the shop runs the legacy PHP checkout, which is not embeddable
  and will not be made so.
* `no-embed-url` — the platform returned no embed URL for a v2 session.
* `no-top-navigation` — this page cannot navigate its own top window (it is
  itself inside a sandboxed builder preview), so a redirect payment method
  could never break out of the frame.
* `handshake-timeout` — the frame never said `ready` within 15 seconds, and
  has been removed. Almost always a `frame-ancestors` refusal: the page
  showing the frame is not the origin the session was created for (a wrong
  `embedOrigin`, `www` against the bare domain, a session resumed on another
  origin). A kit and a checkout that disagree on the message version look
  the same from outside. The browser reports either only inside the frame,
  so this is the host's one way of noticing.

***

### EmbeddedCheckoutHandler()

```ts theme={null}
type EmbeddedCheckoutHandler<T> = (detail) => void;
```

#### Type Parameters

| Type Parameter |
| - |
| `T` *extends* [`EmbeddedCheckoutEventType`](#embeddedcheckouteventtype) |

#### Parameters

| Parameter | Type |
| - | - |
| `detail` | [`EmbeddedCheckoutEventMap`](#embeddedcheckouteventmap)\[`T`] |

#### Returns

`void`

***

### EmbeddedCheckoutInit

```ts theme={null}
type EmbeddedCheckoutInit = 
  | EmbeddedCheckoutInitEmbed
  | EmbeddedCheckoutInitFallback;
```

***

### EmbedErrorCode

```ts theme={null}
type EmbedErrorCode = 
  | "invalid-session"
  | "cannot-embed"
  | "gateway-unavailable"
  | "payment-failed"
  | "unknown";
```

Error codes the FRAME can report. The host adds its own — see `HostErrorCode`.

***

### EmbedFrameMessage

```ts theme={null}
type EmbedFrameMessage = 
  | EmbedReadyMessage
  | EmbedHeightMessage
  | EmbedScrollToMessage
  | EmbedNavigateMessage
  | EmbedRedirectMessage
  | EmbedCompleteMessage
  | EmbedDemoCompleteMessage
  | EmbedErrorMessage
  | EmbedEventMessage
  | EmbedStepMessage;
```

***

### EmbedHostMessage

```ts theme={null}
type EmbedHostMessage = 
  | EmbedInitMessage
  | EmbedViewportMessage
  | EmbedFocusMessage
  | EmbedCartUpdatedMessage;
```

***

### EmbedNavigateReason

```ts theme={null}
type EmbedNavigateReason = "back" | "continue-shopping" | "product";
```

Why the frame wants the host to leave the checkout.

***

### EmbedRedirectMethod

```ts theme={null}
type EmbedRedirectMethod = "GET" | "POST";
```

How a PSP wants to be handed the shopper.

***

### EmbedStep

```ts theme={null}
type EmbedStep = "contact" | "payment";
```

The step the shopper is on inside the checkout.

***

### FieldStatus

```ts theme={null}
type FieldStatus = "empty" | "valid" | "invalid";
```

***

### MaybePromise

```ts theme={null}
type MaybePromise<T> = T | Promise<T>;
```

#### Type Parameters

| Type Parameter |
| - |
| `T` |

***

### MinorUnits

```ts theme={null}
type MinorUnits = number;
```

Every money value crossing this API is an integer in the currency's MINOR
unit (öre, cents). Never a float, never a formatted string. Aliased so the
intent is visible at every use site.

***

### OptionalConsentCategory

```ts theme={null}
type OptionalConsentCategory = Exclude<ConsentCategory, "necessary">;
```

The two categories a shopper actually decides on.

***

### ProductCurrency

```ts theme={null}
type ProductCurrency = CurrencyCode;
```

Every product states the currency its prices are in as `product.currency`
(the client's chosen currency when the shop offers it, else the shop's own).

***

### ProductLookup

```ts theme={null}
type ProductLookup = 
  | {
  id?: undefined;
  slug: string;
}
  | {
  id: string | number;
  slug?: undefined;
} & object;
```

#### Type Declaration

| Name | Type | Description |
| - | - | - |
| `currency?` | `string` \| `null` | Price the product in this currency rather than the client's current one; `null` for the shop's own. See `ProductReadOptions.currency`. |
| `storefrontId?` | `string` \| `null` | Price the product for this campaign storefront rather than the client's own binding; `null` for none. See `ProductReadOptions.storefrontId`. |

***

### ProductSortField

```ts theme={null}
type ProductSortField = "name" | "price" | "createdAt";
```

Sort fields the storefront search accepts. `createdAt` is catalog order.

***

### RelatedProductsMode

```ts theme={null}
type RelatedProductsMode = "category" | "specific" | "none";
```

How the merchant chose a product's related products:

* `category` — products from the same category (the default);
* `specific` — a hand-picked list, in the merchant's order;
* `none` — switched off for this product, or not used by the shop.

***

### SchemaAvailability

```ts theme={null}
type SchemaAvailability = 
  | "https://schema.org/InStock"
  | "https://schema.org/OutOfStock"
  | "https://schema.org/PreOrder";
```

schema.org availability, the vocabulary Google reads for rich results.

***

### SeoOptions

```ts theme={null}
type SeoOptions = SeoInput & SeoDefaults;
```

***

### SessionDataMap

```ts theme={null}
type SessionDataMap = Record<string, DataState>;
```

***

### SessionFieldMap

```ts theme={null}
type SessionFieldMap = Record<string, FieldState>;
```

***

### ShopCurrencyMode

```ts theme={null}
type ShopCurrencyMode = "base" | "display" | "charge";
```

How a currency is offered:

* `"base"` — the shop's own currency.
* `"display"` — prices are SHOWN converted; the checkout charges the shop's
  currency.
* `"charge"` — priced AND charged in this currency.

***

### ShopkitRuntime

```ts theme={null}
type ShopkitRuntime = "browser" | "server";
```

Where is this code executing?

`browser` means there is a real `window` AND a `document` — the two things
the client-side storage adapters actually need. A worker has neither, so it
is deliberately NOT a browser here even though it runs client-side code.

***

### ShopkitScope

```ts theme={null}
type ShopkitScope = keyof typeof SHOPKIT_SCOPES;
```

***

### StartCheckoutResult

```ts theme={null}
type StartCheckoutResult = object & 
  | {
  checkout: "v2";
  legacyOrderUuid?: undefined;
  session: CheckoutSession;
}
  | {
  checkout: "legacy";
  legacyOrderUuid: string;
  session?: undefined;
};
```

Where to send the shopper, and which checkout answered.

`url` is present on both arms, so a "Checkout" button that only navigates
needs no branching at all — and both arms return the shopper to
`<successUrl origin>/success/<orderNumber>` after paying, so neither does the
thank-you page. Branch on `checkout` when the difference matters — chiefly
that a `legacy` handoff has no session to read or patch, and confirms by
`legacyOrderUuid`. See [CheckoutFlavour](#checkoutflavour) and `LegacyHandoff`.

#### Type Declaration

| Name | Type | Description |
| - | - | - |
| `cartId` | `string` | - |
| `checkout` | [`CheckoutFlavour`](#checkoutflavour) | - |
| `embedUrl` | `string` \| `null` | Where to FRAME the checkout, for a caller doing its own embedding. Null whenever there is nothing to frame — no `embed` was asked for, the shop is on the legacy checkout, or the platform has no embed URL for it. [CheckoutResource.mount](#mount) is the supported way to use this. |
| `handoffId` | `string` | The handle to poll [CheckoutResource.confirmation](#confirmation) with: the session id on `v2`, the legacy order uuid on `legacy`. |
| `url` | `string` | Send the shopper here — a top-level navigation, not a fetch. |

***

### StoragePreference

```ts theme={null}
type StoragePreference = 
  | "auto"
  | "cookie"
  | "localStorage"
  | "memory"
  | StorageAdapter;
```

How the SDK should persist ids.

* `"auto"` (default) — in a browser: cookies when writable, else
  `localStorage`, else memory. On a server: the `cookies` accessor when one
  was supplied, else memory. Cookies win over `localStorage` in the browser
  on purpose: they are the only client-side store the SERVER can also read,
  which is what makes an RSC/SSR page see the same cart the browser has.
* `"cookie"` / `"localStorage"` / `"memory"` — force one.
* a `StorageAdapter` — bring your own (Redis, KV, signed cookie, …).

***

### StorefrontRefusal

```ts theme={null}
type StorefrontRefusal = 
  | {
  effectiveState: string;
  reason: "storefront_not_live";
  storefrontId: string | null;
}
  | {
  limitType: "per_order" | "pool";
  maxQuantity: number | null;
  productId: string | null;
  reason: "storefront_quantity_limit";
  storefrontId: string | null;
  variantId: number | null;
}
  | {
  cartStorefrontId: string | null;
  reason: "storefront_mismatch";
  storefrontId: string | null;
};
```

The three ways the platform refuses a campaign storefront checkout, decoded
from the machine-readable `details.reason` beside the error message.

* `storefront_not_live` (409) — the campaign is not open right now.
  `effectiveState` says why: `paused`, `expired`, `coming_soon` (or,
  exceptionally, `draft` / `archived`). Show the campaign-closed treatment;
  there is nothing for the shopper to fix.
* `storefront_quantity_limit` (409) — the cart asks for more of a campaign
  line than the merchant allows. `limitType` decides the recovery:
  `per_order` means "reduce the quantity to at most `maxQuantity`", `pool`
  means the campaign's dedicated stock is down to `maxQuantity` (possibly
  0 — sold out). `productId` is the prefixed id (`prod_123`) of the line.
* `storefront_mismatch` (400) — the checkout named one campaign
  (`storefrontId`) but the cart was created for another
  (`cartStorefrontId`). A programming error, not a shopper condition: bind
  the client to the campaign, or check out a cart created for it.

***

### StorefrontSurface

```ts theme={null}
type StorefrontSurface = typeof STOREFRONT_SURFACES[number];
```

***

### SuccessMode

```ts theme={null}
type SuccessMode = typeof SUCCESS_MODES[number];
```

* `"redirect"` — the default. After payment the hosted checkout sends the
  shopper to `<successUrl origin>/success/<orderNumber>`; your thank-you page
  confirms the order with `confirmation()` / `pollConfirmation()`.
* `"inline"` — the hosted checkout renders the order confirmation itself and
  never redirects. `successUrl` is optional; `parseReturnUrl()` never fires;
  the storefront learns of completion only by polling the confirmation.
  `backUrl` is what brings the shopper home — set it.

The platform echoes the mode back as `successMode` on the session, the
session snapshot and the confirmation. A platform without inline support
rejects a session that has no `successUrl` with a 400, so keep passing
`successUrl` until inline mode is verified on your shop.

***

### VariantSelection

```ts theme={null}
type VariantSelection = Record<number, number>;
```

Which option value is chosen in each option group, keyed by option id.

Partial by nature: a shopper who has picked a colour but not a size has one
entry. A variant is only resolved once every group has one.

## Variables

### ANALYTICS\_SCRIPT\_ATTR

```ts theme={null}
const ANALYTICS_SCRIPT_ATTR: "data-qb-analytics" = "data-qb-analytics";
```

Marks every script tag the kit's destinations add, so a page (or a test) can find them.

***

### CHECKOUT\_SESSION\_PARAM

```ts theme={null}
const CHECKOUT_SESSION_PARAM: "qb_checkout_session" = "qb_checkout_session";
```

The parameter the checkout's return route appends to the merchant's page.

***

### CHECKOUT\_SHOP\_PARAM

```ts theme={null}
const CHECKOUT_SHOP_PARAM: "qb_checkout_shop" = "qb_checkout_shop";
```

The shop's numeric id, appended next to [CHECKOUT\_SESSION\_PARAM](#checkout_session_param). It is
what lets the return leg build `/embed/{storeId}/{sessionId}` with no storage
at all.

***

### CONSENT\_EVENT

```ts theme={null}
const CONSENT_EVENT: "qb:consent" = "qb:consent";
```

The DOM event dispatched on `document` after every decision.

***

### CONSENT\_LABEL\_LANGUAGES

```ts theme={null}
const CONSENT_LABEL_LANGUAGES: readonly string[];
```

The languages the default banner has copy for.

***

### CONSENT\_STYLE\_ID

```ts theme={null}
const CONSENT_STYLE_ID: "qb-consent-styles" = "qb-consent-styles";
```

The default banner's stylesheet, shared by `<ConsentBanner>` and
`<qb-consent-banner>` so one set of rules (and one set of custom
properties) styles both.

Three rules about how it gets along with a site's own CSS:

* Everything sits in `@layer qb-consent`. Unlayered styles beat layered
  ones whatever their specificity, so any rule a site writes for a
  `.qb-consent*` class wins without `!important` or a longer selector.
* The knobs are custom properties, read with a fallback, so setting one on
  `:root` (or on the banner) is enough: `--qb-consent-bg`, `-fg`, `-muted`,
  `-border`, `-accent`, `-accent-fg`, `-radius`, `-button-radius`, `-font`,
  `-font-size`, `-padding`, `-gap`, `-offset`, `-max-width`, `-shadow`,
  `-focus`, `-z-index`. The built-in colours follow
  `prefers-color-scheme`; a colour you set applies in both schemes.
* It only matches the default markup, and not when that markup carries
  `qb-consent--unstyled` (the `unstyled` prop / attribute), so a page that
  wants none of it gets none of it.

Accept and reject share one look on purpose: an "accept all" more
prominent than "only necessary" is exactly what EU regulators object to.

***

### CONSENT\_STYLES

```ts theme={null}
const CONSENT_STYLES: string;
```

***

### CONSENT\_UPDATE\_EVENT

```ts theme={null}
const CONSENT_UPDATE_EVENT: "qb_consent_update" = "qb_consent_update";
```

Pushed to the dataLayer on every consent change, for GTM triggers.

***

### DEFAULT\_API\_URL

```ts theme={null}
const DEFAULT_API_URL: "https://commerce.quickbutik.com" = "https://commerce.quickbutik.com";
```

Quickbutik's public commerce API host. Used when `apiUrl` is omitted.

This is the storefront half of the platform API: it answers CORS preflights
for any origin and accepts publishable keys only, so a browser can call it
straight from the shop's own domain with no proxy in between. The merchant
API — personal access tokens, admin scopes, no wildcard CORS — is a separate
host, `https://api.quickbutik.com`, and is not what this SDK talks to.

***

### DEFAULT\_CHECKOUT\_URL

```ts theme={null}
const DEFAULT_CHECKOUT_URL: "https://pay.quickbutik.com" = "https://pay.quickbutik.com";
```

Quickbutik's current hosted checkout (checkout-v2). Used when `checkoutUrl` is
omitted.

Up to 1.1.0 this named `checkout.quickbutik.com`, which is the LEGACY
checkout — a different application that reads the last path segment as an
order id, not a checkout session id, so every URL built from it answered
"we couldn't find it". Shops on the legacy checkout are no longer served by
guessing a host at all: `checkout.start()` asks the platform, which knows
which checkout the shop runs and returns the matching URL. This constant is
only the last-resort fallback for the checkout-v2 flavour.

***

### DEFAULT\_CONSENT\_COOKIE

```ts theme={null}
const DEFAULT_CONSENT_COOKIE: "qb_consent" = "qb_consent";
```

The decision lives in a cookie, not in localStorage, for one reason: a
server can read it. A storefront that renders on the server can then paint
the page with the right banner state on the first byte — no banner for a
shopper who already decided, no flash for one who has not — and a server
action can forward the decision to the hosted checkout without a round
trip through the browser.

The format is the kit's own (not the hosted storefront's `cc_cookie`): the
two cookies never share a hostname, and this one carries the revision and
the timestamp a compliance review asks for.

***

### DEFAULT\_CONSENT\_MAX\_AGE\_DAYS

```ts theme={null}
const DEFAULT_CONSENT_MAX_AGE_DAYS: 180 = 180;
```

Six months. Long enough that a returning shopper is not asked on every
visit, short enough to re-collect within the window most EU regulators
consider reasonable. Configurable per shop.

***

### DEFAULT\_CONSENT\_REVISION

```ts theme={null}
const DEFAULT_CONSENT_REVISION: 1 = 1;
```

***

### DEFAULT\_SHOPKIT\_SCOPES

```ts theme={null}
const DEFAULT_SHOPKIT_SCOPES: readonly ShopkitScope[];
```

The set every storefront needs to browse, build a cart and hand off to the
hosted checkout. Matches the default of the platform's publishable-key
minting (`create-token.ts --type publishable`), minus `storefront:read`.

***

### DEFAULT\_STOREFRONT\_SURFACE

```ts theme={null}
const DEFAULT_STOREFRONT_SURFACE: StorefrontSurface;
```

The surface sent when a storefront is bound and none was named.

***

### EMBED\_MESSAGE\_SOURCE

```ts theme={null}
const EMBED_MESSAGE_SOURCE: "qb-checkout-embed" = "qb-checkout-embed";
```

The discriminator. A page hosting a checkout also receives messages from
Adyen, browser extensions and devtools; anything without this is not ours.

Distinct from the admin preview's `qb-checkout-preview` on purpose: the
preview is a render target with no session and no network, the embed is the
real checkout taking real money, and a message meant for one must never be
readable as a message for the other.

***

### EMBED\_MESSAGE\_VERSION

```ts theme={null}
const EMBED_MESSAGE_VERSION: 1 = 1;
```

The only envelope version in existence.

Both sides ignore an envelope whose version they do not implement, so a new
kit against an old checkout (or the reverse) degrades to "the frame never
becomes ready" — a `handshake-timeout` fallback after 15 seconds, which takes
the frame down and opens the hosted checkout — rather than to a misread
payload.

***

### PUBLISHABLE\_KEY\_PREFIX

```ts theme={null}
const PUBLISHABLE_KEY_PREFIX: "qb_pk_" = "qb_pk_";
```

Prefix every Quickbutik publishable key carries.

***

### SHOPKIT\_SCOPES

```ts theme={null}
const SHOPKIT_SCOPES: object;
```

The scopes a publishable key can usefully carry for a headless storefront,
mirroring `services/gateway/src/config/scopes.ts`. Only this subset is
reachable with an `actorType: 'storefront'` credential — the write scopes for
products, orders and customers are merchant-only by design and a publishable
key presenting them is rejected at the gateway.

#### Type Declaration

| Name | Type |
| - | - |
| <a id="cartread" /> `cart:read` | `"Read the shopper's cart"` |
| <a id="cartwrite" /> `cart:write` | `"Create carts and add/update/remove items"` |
| <a id="checkoutread" /> `checkout:read` | `"Read a checkout session and poll its order confirmation"` |
| <a id="checkoutwrite" /> `checkout:write` | `"Create checkout sessions"` |
| <a id="productsread" /> `products:read` | `"Read products, variants, images and categories"` |
| <a id="storefrontread" /> `storefront:read` | `"Read pages, navigation and theme content"` |

***

### STOREFRONT\_ID\_PATTERN

```ts theme={null}
const STOREFRONT_ID_PATTERN: RegExp;
```

`sf_` + 26 characters of Crockford base32 (no I, L, O, U). Case-insensitive:
the platform accepts either case and stores one canonical spelling.

***

### STOREFRONT\_SURFACES

```ts theme={null}
const STOREFRONT_SURFACES: readonly ["hosted", "link", "embed", "shopkit", "agentic"];
```

Where a campaign storefront was presented to the shopper. Attribution, not
behaviour.

* `hosted`  — the campaign's own page on the shop's domain.
* `link`    — a short or shared link that lands on the campaign.
* `embed`   — rendered inside a third-party page (iframe / script embed).
* `shopkit` — inside a storefront built on this SDK. The default here.
* `agentic` — reached by an AI agent acting for the shopper.

***

### SUCCESS\_MODES

```ts theme={null}
const SUCCESS_MODES: readonly ["redirect", "inline"];
```

How the hosted checkout ends: send the shopper back, or show the order itself.

## Functions

### absoluteUrl()

```ts theme={null}
function absoluteUrl(url, baseUrl): string | undefined;
```

Resolve a possibly-relative URL against the storefront's origin.

Returns undefined rather than a relative value when there is no base: a
relative `<link rel="canonical">` is worse than none, because it resolves
against whatever URL the crawler happens to be on.

#### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` \| `undefined` |
| `baseUrl` | `string` \| `undefined` |

#### Returns

`string` | `undefined`

***

### appendCheckoutHandoffParams()

```ts theme={null}
function appendCheckoutHandoffParams(
   url, 
   consent, 
   options?): string;
```

The hosted checkout URL with the shopper's consent — and, when analytics
is allowed, the GA session link — appended the way the hosted storefront
appends them: `consentCategories=analytics,marketing`, `gaClientId`,
`gaSessionId`. The checkout reads them on arrival and sets Consent Mode
before any tag loads.

An undecided shopper is reported as `consentCategories=` (present, empty):
a signal that nothing was granted, so the checkout runs denied. Only
`requireConsent: false` omits the parameter, which makes the checkout track
ungated. The legacy hosted checkout ignores these parameters.

Idempotent: applying it twice leaves the URL the second call describes, so
a URL a server client already decorated can be decorated again in the
browser without stacking or contradicting itself.

Returns the URL untouched when it cannot be parsed.

#### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` |
| `consent` | [`ConsentState`](#consentstate-1) \| `null` \| `undefined` |
| `options?` | [`HandoffParamsOptions`](#handoffparamsoptions) |

#### Returns

`string`

***

### applyImageTransform()

```ts theme={null}
function applyImageTransform(
   url, 
   transform?, 
   cacheBust?): string;
```

Add CDN transform parameters to an image URL, keeping any it already has.

Exported for the case this module does not cover — a hero crop, an og:image
built by hand — so the parameter names live in exactly one place.

#### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` |
| `transform?` | [`ImageTransform`](#imagetransform) |
| `cacheBust?` | `string` \| `null` |

#### Returns

`string`

***

### applyTitleTemplate()

```ts theme={null}
function applyTitleTemplate(
   title, 
   siteName, 
   template): string;
```

Compose the document title. `%s` is the page title; the default appends the
site name, and a page whose title already IS the site name is left alone so
the homepage does not read "Min butik · Min butik".

#### Parameters

| Parameter | Type |
| - | - |
| `title` | `string` |
| `siteName` | `string` \| `undefined` |
| `template` | `string` \| `undefined` |

#### Returns

`string`

***

### bareProductId()

```ts theme={null}
function bareProductId(id): string;
```

`"prod_27"` → `"27"`; a number passes through as its string.

#### Parameters

| Parameter | Type |
| - | - |
| `id` | `string` \| `number` |

#### Returns

`string`

***

### breadcrumbJsonLd()

```ts theme={null}
function breadcrumbJsonLd(items): Record<string, unknown> | undefined;
```

schema.org `BreadcrumbList`.

Positions are 1-based and must be contiguous, so entries without a name are
dropped before numbering rather than leaving a hole.

#### Parameters

| Parameter | Type |
| - | - |
| `items` | [`SeoBreadcrumb`](#seobreadcrumb)\[] |

#### Returns

`Record`\<`string`, `unknown`> | `undefined`

***

### buildImageSrcSet()

```ts theme={null}
function buildImageSrcSet(image, options?): string | null;
```

A `srcSet` for one image, or null when there is nothing to build one from.

Density descriptors (the default) are right for an image rendered at a fixed
CSS size — a thumbnail, a card at a known column width. Width descriptors
plus `sizes` are right for one that reflows with the viewport.

#### Parameters

| Parameter | Type |
| - | - |
| `image` | [`ProductImage`](#productimage) \| `null` \| `undefined` |
| `options?` | [`ImageSrcSetOptions`](#imagesrcsetoptions) |

#### Returns

`string` | `null`

***

### buildPriceState()

```ts theme={null}
function buildPriceState(product, selection): VariantPriceState;
```

Price for the current selection: the exact figure once a variant is resolved,
and the range across everything still reachable before that.

Variant prices win over the product-level price, falling back to it when a
variant carries none — which is how the platform models "all variants cost
the same".

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `selection` | [`VariantSelection`](#variantselection) |

#### Returns

[`VariantPriceState`](#variantpricestate)

***

### buildSeo()

```ts theme={null}
function buildSeo(options): SeoTags;
```

Everything a page needs in `<head>`, from Quickbutik's own data.

Prefers what the merchant wrote — `seoTitle` / `seoDescription` are fields
they filled in for exactly this purpose and are otherwise invisible to a
headless storefront — and falls back to the product name and description.

#### Parameters

| Parameter | Type |
| - | - |
| `options` | [`SeoOptions`](#seooptions) |

#### Returns

[`SeoTags`](#seotags)

***

### buildVariantMatrix()

```ts theme={null}
function buildVariantMatrix(product, selection): VariantOptionGroupState[];
```

The full picker state: every group, every value, and whether each is selected
and still reachable.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `selection` | [`VariantSelection`](#variantselection) |

#### Returns

[`VariantOptionGroupState`](#variantoptiongroupstate)\[]

***

### canNavigateTopWindow()

```ts theme={null}
function canNavigateTopWindow(): boolean;
```

True when this page can navigate its own top window.

Three cases, and only the last is a problem:

* this page IS the top window (the normal case) — yes;
* it is framed by a document it can reach (same origin) — yes, and reading
  `top.location.href` proves it;
* it is framed cross-origin, or sandboxed without `allow-top-navigation` —
  the read throws `SecurityError`, and a sandboxed frame could not navigate
  even if it could read.

The embedded checkout needs this: a redirect payment method takes the TOP
window to the provider, so a page that cannot move its own top — an AI site
builder's preview pane, typically — must fall back to the redirect checkout
rather than frame one the shopper could get stuck in.

Probed rather than inferred, like `hasLocalStorage` above: the property
exists in every case, and only touching it tells you the truth.

#### Returns

`boolean`

***

### cartLineDiff()

```ts theme={null}
function cartLineDiff(before, after): CartLineDiff;
```

What changed between two carts, as the items that were added and the items
that were removed — with the QUANTITY that changed, not the line's total.
Keyed by product + variant, so a server that merged a second "add" into an
existing line still yields "one more of this", and a cart that went away
(`after: null`) yields every line as removed.

#### Parameters

| Parameter | Type |
| - | - |
| `before` | [`Cart`](#cart-1) \| `null` \| `undefined` |
| `after` | [`Cart`](#cart-1) \| `null` \| `undefined` |

#### Returns

[`CartLineDiff`](#cartlinediff)

***

### clearConsentCookie()

```ts theme={null}
function clearConsentCookie(options?): void;
```

Expire the decision cookie. No-op outside a browser.

#### Parameters

| Parameter | Type |
| - | - |
| `options?` | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions) |

#### Returns

`void`

***

### clearOptionValue()

```ts theme={null}
function clearOptionValue(selection, optionId): VariantSelection;
```

Remove one group's choice, leaving the rest intact.

#### Parameters

| Parameter | Type |
| - | - |
| `selection` | [`VariantSelection`](#variantselection) |
| `optionId` | `number` |

#### Returns

[`VariantSelection`](#variantselection)

***

### clearProductCache()

```ts theme={null}
function clearProductCache(client, lookup?): void;
```

Forget a client's cached lookups — one product's, or all of them.

Entries live as long as the client, which is right for a per-request server
client and wrong for a long-lived browser session that must pick up an edited
product. Call this after a revalidation.

The client is required rather than optional: the cache is a `WeakMap` and
cannot be enumerated, so a "clear everything" call would have to be a silent
no-op.

#### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](#shopkitclient) |
| `lookup?` | [`ProductLookup`](#productlookup) |

#### Returns

`void`

***

### clearShopCache()

```ts theme={null}
function clearShopCache(client): void;
```

Forget a client's cached shop, so the next read goes to the API.

#### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](#shopkitclient) |

#### Returns

`void`

***

### consentCategoriesParam()

```ts theme={null}
function consentCategoriesParam(state): string | null;
```

The `consentCategories` value the hosted checkout reads off its URL.

Three answers, and they mean different things to the checkout:

* `null` — no consent signal at all. The checkout tracks ungated, exactly
  as it does for a hosted shop without the consent app. Only for a
  storefront that has opted out of consent (`requireConsent: false`).
* `""` — a signal, with nothing granted. Undecided is reported this way:
  the checkout must not track a shopper who has not said yes.
* `"analytics,marketing"` — the granted subset.

#### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) \| `null` \| `undefined` |

#### Returns

`string` | `null`

***

### consentLabels()

```ts theme={null}
function consentLabels(lang?, overrides?): ConsentLabels;
```

The labels for a language, with overrides applied.

`lang` is a BCP 47 tag or a bare code; only the primary subtag matters
(`sv-SE` → `sv`), Norwegian's `no`/`nn` fold into `nb`, and anything
unknown falls back to English. Left out, it is read from `<html lang>` in a
browser, so a storefront that already sets its document language gets the
right copy for free.

#### Parameters

| Parameter | Type |
| - | - |
| `lang?` | `string` \| `null` |
| `overrides?` | `Partial`\<[`ConsentLabels`](#consentlabels)> \| `null` |

#### Returns

[`ConsentLabels`](#consentlabels)

***

### consentModeSignals()

```ts theme={null}
function consentModeSignals(state): ConsentModeSignals;
```

The signals for a state. Null and undecided are everything denied.

#### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) \| `null` \| `undefined` |

#### Returns

[`ConsentModeSignals`](#consentmodesignals)

***

### consumeReturnedSessionId()

```ts theme={null}
function consumeReturnedSessionId(): string | null;
```

Read and strip in one step.

For a host built by hand that does not wait for the frame's `ready` before
deciding the return leg is over. The kit's own surfaces no longer use it:
`<qb-checkout>` and `<Checkout>` read with [readReturnedCheckout](#readreturnedcheckout) on
load and call [stripReturnedSessionId](#stripreturnedsessionid) from their `ready` handler, so a
resume that fails before the frame answers leaves the parameters — and the
reload that follows something to resume. Prefer that order.

Not idempotent on purpose: the second call returns null. Hold the value
rather than calling it again. Returns the session id alone, as it always
has; read the shop id with [readReturnedCheckout](#readreturnedcheckout) BEFORE calling this.

#### Returns

`string` | `null`

***

### createCookieStorage()

```ts theme={null}
function createCookieStorage(accessor, attributes?): StorageAdapter;
```

Server-side cookie storage driven by an injected accessor.

```ts theme={null}
// Next.js app router
import { cookies } from "next/headers"
createCookieStorage({
  get: async (name) => (await cookies()).get(name)?.value ?? null,
  set: async (name, value, attrs) => { (await cookies()).set(name, value, attrs) },
  remove: async (name) => { (await cookies()).delete(name) },
})
```

#### Parameters

| Parameter | Type |
| - | - |
| `accessor` | [`CookieAccessor`](#cookieaccessor) |
| `attributes?` | [`CookieAttributes`](#cookieattributes) |

#### Returns

[`StorageAdapter`](#storageadapter)

***

### createDocumentCookieStorage()

```ts theme={null}
function createDocumentCookieStorage(attributes?): StorageAdapter;
```

`document.cookie`. Browser-only; readable by the server on the next request.

#### Parameters

| Parameter | Type |
| - | - |
| `attributes?` | [`CookieAttributes`](#cookieattributes) |

#### Returns

[`StorageAdapter`](#storageadapter)

***

### createLocalStorage()

```ts theme={null}
function createLocalStorage(): StorageAdapter;
```

`window.localStorage`, with every access guarded (see `hasLocalStorage`).

#### Returns

[`StorageAdapter`](#storageadapter)

***

### createMemoryStorage()

```ts theme={null}
function createMemoryStorage(): StorageAdapter;
```

In-process map. The always-available fallback; never survives a restart.

#### Returns

[`StorageAdapter`](#storageadapter)

***

### createRequestCookieStorage()

```ts theme={null}
function createRequestCookieStorage(source, options?): StorageAdapter;
```

Read cookies straight off a `Request` (or any `Headers`), optionally
collecting writes as `Set-Cookie` strings for the caller to attach to its
response. Framework-free — the fit for Astro endpoints, TanStack Start
server functions, plain `fetch` handlers and Workers.

```ts theme={null}
const setCookies: string[] = []
const storage = createRequestCookieStorage(request, { collect: setCookies })
// … after rendering:
for (const c of setCookies) headers.append("set-cookie", c)
```

#### Parameters

| Parameter | Type |
| - | - |
| `source` | `CookieHeaderSource` |
| `options?` | \{ `attributes?`: [`CookieAttributes`](#cookieattributes); `collect?`: `string`\[]; } |
| `options.attributes?` | [`CookieAttributes`](#cookieattributes) |
| `options.collect?` | `string`\[] |

#### Returns

[`StorageAdapter`](#storageadapter)

***

### createScopeGuard()

```ts theme={null}
function createScopeGuard(scopes): ScopeGuard;
```

Build the pre-flight scope checker.

Passing `scopes: null` turns enforcement off — useful when the key's scopes
are decided by an admin and the app genuinely does not know them. The gateway
still enforces; all that is lost is the friendly local error.

#### Parameters

| Parameter | Type |
| - | - |
| `scopes` | readonly `string`\[] \| `null` \| `undefined` |

#### Returns

[`ScopeGuard`](#scopeguard)

***

### createShopkitClient()

```ts theme={null}
function createShopkitClient(config): ShopkitClient;
```

Create a storefront client.

```ts theme={null}
const shopkit = createShopkitClient({
  publishableKey: process.env.NEXT_PUBLIC_QUICKBUTIK_PUBLISHABLE_KEY!,
  scopes: ["products:read", "cart:read", "cart:write", "checkout:read", "checkout:write"],
})
```

#### Parameters

| Parameter | Type |
| - | - |
| `config` | [`ShopkitConfig`](#shopkitconfig) |

#### Returns

[`ShopkitClient`](#shopkitclient)

***

### currencyDecimals()

```ts theme={null}
function currencyDecimals(currency): number;
```

#### Parameters

| Parameter | Type |
| - | - |
| `currency` | `string` |

#### Returns

`number`

***

### deleteDocumentCookie()

```ts theme={null}
function deleteDocumentCookie(name, attributes?): void;
```

Expire one cookie in `document.cookie`. No-op outside a browser.

#### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |
| `attributes?` | [`CookieAttributes`](#cookieattributes) |

#### Returns

`void`

***

### describeCurrency()

```ts theme={null}
function describeCurrency(shop, selected): CurrencyInfo;
```

Resolve a currency choice against what the shop offers — the same rule the
platform applies: a code the shop does not offer (converter off, not
enabled, no rate) silently falls back to the shop's currency.

```ts theme={null}
const shop = await shopkit.shop.get()
describeCurrency(shop, shopkit.currency)
// { currency: "EUR", baseCurrency: "SEK", mode: "charge", rate: 0.0871, … }
```

#### Parameters

| Parameter | Type |
| - | - |
| `shop` | \| `Pick`\<[`Shop`](#shop-2), `"currency"` \| `"currencies"`> \| `null` \| `undefined` |
| `selected` | `string` \| `null` \| `undefined` |

#### Returns

[`CurrencyInfo`](#currencyinfo)

***

### destinationsFromShop()

```ts theme={null}
function destinationsFromShop(tracking, options?): AnalyticsDestination[];
```

The destinations a shop's tracking configuration asks for: Google when a
GA4 measurement id or a GTM container is set, the Meta Pixel when a pixel
id is. The ids are the merchant's own, entered in the admin, already
validated by the platform — and the same ones the hosted checkout loads, so
the storefront and the checkout report into the same properties.

Empty for a shop with nothing configured and for a platform older than the
field. `metaCapiEnabled` is informational here: the Conversions API is
sent by the platform for the hosted checkout's own events, not from a
storefront.

#### Parameters

| Parameter | Type |
| - | - |
| `tracking` | `ShopTracking` \| `null` \| `undefined` |
| `options?` | [`DestinationsFromShopOptions`](#destinationsfromshopoptions) |

#### Returns

[`AnalyticsDestination`](#analyticsdestination)\[]

***

### detectRuntime()

```ts theme={null}
function detectRuntime(): ShopkitRuntime;
```

#### Returns

[`ShopkitRuntime`](#shopkitruntime)

***

### firstAvailableVariant()

```ts theme={null}
function firstAvailableVariant(product): ProductVariant | null;
```

The first purchasable variant, for storefronts that preselect one.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |

#### Returns

[`ProductVariant`](#productvariant) | `null`

***

### formatMoney()

```ts theme={null}
function formatMoney(
   amount, 
   currency, 
   options?): string;
```

Minor units → a display string in the shopper's locale.

The React layer deliberately has no such helper: a hook hands back
`{ amount, currency }` and the app formats it however its design system
says. HTML bindings have no such escape — `data-qb-text="price.formatted"`
needs a string — so the elements layer must be able to produce one, and this
is it.

```ts theme={null}
formatMoney(129900, "SEK", { locale: "sv-SE" })  // "1 299,00 kr"
formatMoney(990, "JPY", { locale: "ja-JP" })     // "￥990"  — no decimals
formatMoney(129900, "SEK", { symbol: false })    // "1 299,00"
```

The decimal count comes from [currencyDecimals](#currencydecimals) rather than a hardcoded
100, for the same reason the SEO builder needs it: JPY and ISK have no minor
unit, and dividing those by 100 states a price that is a hundred times too
small.

#### Parameters

| Parameter | Type |
| - | - |
| `amount` | `number` |
| `currency` | `string` |
| `options?` | [`FormatMoneyOptions`](#formatmoneyoptions) |

#### Returns

`string`

***

### formatMoneyRange()

```ts theme={null}
function formatMoneyRange(
   min, 
   max, 
   currency, 
   options?): string | null;
```

A price range, or a single figure when both ends agree.

What a "från 99 kr" label needs: `VariantPriceState` reports `min`, `max` and
`isRange`, and every storefront then writes the same three-branch formatter.

#### Parameters

| Parameter | Type |
| - | - |
| `min` | `number` \| `null` |
| `max` | `number` \| `null` |
| `currency` | `string` |
| `options?` | [`FormatMoneyOptions`](#formatmoneyoptions) |

#### Returns

`string` | `null`

***

### formatSchemaPrice()

```ts theme={null}
function formatSchemaPrice(amount, currency): string;
```

Minor units → the decimal string schema.org and OpenGraph expect (`"99.00"`).

A string, not a number: `price` in schema.org is text, and a float would
reintroduce exactly the rounding error minor units exist to avoid.

#### Parameters

| Parameter | Type |
| - | - |
| `amount` | `number` |
| `currency` | `string` |

#### Returns

`string`

***

### getOptionGroups()

```ts theme={null}
function getOptionGroups(product): ProductOptionType[];
```

The option groups for a product, in display order.

`product.options` is optional on the wire, so the groups are reconstructed
from the variants' own option values when it is absent — otherwise a product
that omits the top-level list would render no picker at all.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |

#### Returns

[`ProductOptionType`](#productoptiontype)\[]

***

### getProductPromise()

```ts theme={null}
function getProductPromise(client, lookup): Promise<Product | null>;
```

The (cached) promise for one product lookup.

A rejected promise is evicted so a retry — a remount after an error boundary
reset, say — can actually re-request rather than replaying the failure
forever. The original promise is still what the caller gets, so the rejection
surfaces exactly once per attempt and never as an unhandled rejection.

#### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](#shopkitclient) |
| `lookup` | [`ProductLookup`](#productlookup) |

#### Returns

`Promise`\<[`Product`](#product-1) | `null`>

***

### getShopPromise()

```ts theme={null}
function getShopPromise(client): Promise<Shop>;
```

#### Parameters

| Parameter | Type |
| - | - |
| `client` | [`ShopkitClient`](#shopkitclient) |

#### Returns

`Promise`\<[`Shop`](#shop-2)>

***

### googleTagDestination()

```ts theme={null}
function googleTagDestination(options): AnalyticsDestination;
```

GA4, Google Ads and Google Tag Manager, through one `gtag` and one
dataLayer. Consent Mode v2 is set up BEFORE any script is injected: a
denied `default` with `wait_for_update`, then — when the shopper has
already decided — an `update` straight after. The pair matters: a
merchant's own GTM container ships region-scoped consent defaults applied
at container init, and a `default` never outranks another `default`, so a
lone granted default would be silently discarded and every Google tag
would run cookieless. Only an `update` outranks a default.

Events: with a GA4 measurement id (or an Ads id) they go through
`gtag('event', …)`, which a GTM container on the same page also sees. With
a container ONLY, they are pushed as `{ event, ecommerce }` with the GA4
`ecommerce: null` reset in between. Never both — a container that hosts a
GA4 tag would otherwise count every event twice.

#### Parameters

| Parameter | Type |
| - | - |
| `options` | [`GoogleTagOptions`](#googletagoptions) |

#### Returns

[`AnalyticsDestination`](#analyticsdestination)

***

### hasDocumentCookies()

```ts theme={null}
function hasDocumentCookies(): boolean;
```

True when `document.cookie` is readable/writable — the browser half of the
cookie story. The server half needs an injected accessor (see
`createCookieStorage`), because there is no ambient request to read from.

#### Returns

`boolean`

***

### hasLocalStorage()

```ts theme={null}
function hasLocalStorage(): boolean;
```

True when `localStorage` is present AND usable. Presence alone is not
enough: Safari private mode and "block all cookies" both expose the object
and throw on write, and an iframe on a partitioned origin throws on mere
access. The probe is the only reliable answer.

#### Returns

`boolean`

***

### injectConsentStyles()

```ts theme={null}
function injectConsentStyles(doc?): void;
```

Put the default banner's stylesheet in `<head>`, once per document. A
no-op outside a browser and when it is already there. The React banner
does not need this (it renders the same rules as a hoisted `<style>`, so
they are in the server HTML too); it is for `<qb-consent-banner>` and for
a page of your own that reuses the default class names.

#### Parameters

| Parameter | Type |
| - | - |
| `doc?` | `Document` |

#### Returns

`void`

***

### injectScript()

```ts theme={null}
function injectScript(src, id): HTMLScriptElement | null;
```

Add a vendor script to `<head>`, once per `src` for the lifetime of the
page. A second call for the same URL — from a second hub, a re-`configure`,
a React StrictMode double effect — is a no-op; so is a call outside a
browser. The tag is `async` and carries `data-qb-analytics="<id>"`.

The CSP consequence is the storefront's to carry: a page with a
`script-src` must allow the vendor hosts it opts into (see the consent and
analytics guide). The kit never evaluates strings.

#### Parameters

| Parameter | Type |
| - | - |
| `src` | `string` |
| `id` | `string` |

#### Returns

`HTMLScriptElement` | `null`

***

### isBrowser()

```ts theme={null}
function isBrowser(): boolean;
```

True in a DOM-bearing browser context.

Guarded with `typeof` on purpose: bundlers targeting server runtimes replace
`window` with nothing at all rather than `undefined`, so a bare reference
throws instead of returning false.

#### Returns

`boolean`

***

### isSafeNavigationUrl()

```ts theme={null}
function isSafeNavigationUrl(value): value is string;
```

Is this something the host may navigate to?

Every `url` in the contract — `navigate.url`, `redirect.url` — is checked on
**both** sides before any navigation, and this is the host's copy of that
check. Not defence in depth for its own sake: the frame validates what it
sends, but the host is the party that actually performs the navigation, and
it is the one holding the merchant's origin. A `javascript:` URL handed to
`location.assign` executes in THIS document — the merchant's page, with the
merchant's cookies — so the check has to live where the navigation does.

`https:` only, with `http:` allowed for loopback so a rig and a `vite dev`
page work. Relative URLs are rejected rather than resolved: the contract says
absolute, and resolving an attacker-chosen path against the host page is a
more surprising outcome than dropping the message.

This must agree with the frame's copy EXACTLY, down to which hostnames count
as loopback. A host that is stricter than the frame does not fail loudly — it
drops a message the frame was happy to send, and a dropped message makes no
sound at all. That lands on a developer running their storefront on
`http://app.localhost:5173`, in local development, where the redirect
break-out is the least likely thing to be exercised by hand. So the function
below is a port of `isLoopbackHostname` in the frame's `embed-messages.ts`,
not an equivalent of it.

#### Parameters

| Parameter | Type |
| - | - |
| `value` | `unknown` |

#### Returns

`value is string`

***

### isStorefrontId()

```ts theme={null}
function isStorefrontId(value): value is string;
```

True for a well-formed campaign storefront id. Says nothing about whether it exists.

#### Parameters

| Parameter | Type |
| - | - |
| `value` | `unknown` |

#### Returns

`value is string`

***

### isStorefrontSurface()

```ts theme={null}
function isStorefrontSurface(value): value is "embed" | "link" | "hosted" | "shopkit" | "agentic";
```

#### Parameters

| Parameter | Type |
| - | - |
| `value` | `unknown` |

#### Returns

value is "embed" | "link" | "hosted" | "shopkit" | "agentic"

***

### itemFromCartLine()

```ts theme={null}
function itemFromCartLine(
   line, 
   currency, 
   quantity?): CommerceItem;
```

#### Parameters

| Parameter | Type |
| - | - |
| `line` | [`CartItem`](#cartitem) |
| `currency` | `string` |
| `quantity?` | `number` |

#### Returns

[`CommerceItem`](#commerceitem)

***

### itemFromProduct()

```ts theme={null}
function itemFromProduct(
   product, 
   variant, 
   currency?, 
   quantity?): CommerceItem;
```

One GA4 item for a product (and the variant the shopper is looking at, when
there is one). The price is the variant's when known, else the product's.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | `Pick`\<[`Product`](#product-1), `"id"` \| `"name"` \| `"price"`> |
| `variant` | \| `Pick`\<[`ProductVariant`](#productvariant), `"id"` \| `"price"` \| `"options"`> \| `null` \| `undefined` |
| `currency?` | `string` \| `null` |
| `quantity?` | `number` |

#### Returns

[`CommerceItem`](#commerceitem)

***

### itemsFromCart()

```ts theme={null}
function itemsFromCart(cart): CommerceItem[];
```

The cart's lines as GA4 items.

#### Parameters

| Parameter | Type |
| - | - |
| `cart` | [`Cart`](#cart-1) \| `null` \| `undefined` |

#### Returns

[`CommerceItem`](#commerceitem)\[]

***

### itemsFromCheckoutProducts()

```ts theme={null}
function itemsFromCheckoutProducts(products, currency): CommerceItem[];
```

A checkout session's snapshotted lines (`data.cart_products`) as GA4 items.

#### Parameters

| Parameter | Type |
| - | - |
| `products` | \| readonly [`CheckoutCartProduct`](#checkoutcartproduct)\[] \| `null` \| `undefined` |
| `currency` | `string` |

#### Returns

[`CommerceItem`](#commerceitem)\[]

***

### itemsParams()

```ts theme={null}
function itemsParams(items, currency?): CommerceEventParams;
```

`{ currency, value, items }` for a set of items — the shape of every cart event.

#### Parameters

| Parameter | Type |
| - | - |
| `items` | [`CommerceItem`](#commerceitem)\[] |
| `currency?` | `string` |

#### Returns

[`CommerceEventParams`](#commerceeventparams-1)

***

### metaPixelDestination()

```ts theme={null}
function metaPixelDestination(options): AnalyticsDestination;
```

The Meta Pixel, hard-gated on `marketing`: nothing — not the script, not
`PageView` — happens before the shopper grants it, and nothing more is
sent after they withdraw it. The event names are Meta's standard events,
mapped from the GA4 vocabulary the way the hosted storefront maps them:

| kit event | Meta event |
| - | - |
| `page_view` | `PageView` |
| `view_item` | `ViewContent` |
| `view_item_list` | `ViewCategory` (custom) |
| `search` | `Search` |
| `add_to_cart` | `AddToCart` |
| `begin_checkout` | `InitiateCheckout` |
| `add_payment_info` | `AddPaymentInfo` |
| purchase | `Purchase` with `eventID` |

Meta's own consent API follows the decision: `fbq("consent", "grant")`
before `init`, and `revoke` / `grant` on every later change, so a
withdrawal also stops the pixel's cookies, not just the kit's events.

`remove_from_cart` and the shipping step have no Meta equivalent and are
dropped. The purchase `eventID` is `purchase_{storeId}_{orderNumber}`, the
id the platform's own server-side events use, so Meta merges them; with no
store id known the `Purchase` carries no `eventID` at all.

#### Parameters

| Parameter | Type |
| - | - |
| `options` | [`MetaPixelOptions`](#metapixeloptions) |

#### Returns

[`AnalyticsDestination`](#analyticsdestination)

***

### normalizeApiUrl()

```ts theme={null}
function normalizeApiUrl(url): string;
```

Strip trailing slashes and a trailing `/v2`. Every request path in this SDK
carries its own `/v2` prefix, so an `apiUrl` that already ends in `/v2` would
otherwise produce `/v2/v2/...` and 404 — a mistake operators make often
enough that the platform's own checkout normalizes for it too.

#### Parameters

| Parameter | Type |
| - | - |
| `url` | `string` |

#### Returns

`string`

***

### normalizeCurrency()

```ts theme={null}
function normalizeCurrency(value, context?): string | null;
```

`"eur"` / `" EUR "` → `"EUR"`; `null`, `undefined` and `""` → null.

Throws a `ShopkitConfigError` for anything that is not three letters: the
platform rejects a malformed code with a 400, and a typo in a config is
better reported here, by name, than as a failed product grid.

#### Parameters

| Parameter | Type |
| - | - |
| `value` | `string` \| `null` \| `undefined` |
| `context?` | `string` |

#### Returns

`string` | `null`

***

### organizationJsonLd()

```ts theme={null}
function organizationJsonLd(shop, options?): Record<string, unknown>;
```

schema.org `Organization` — the shop itself, for the knowledge panel.

#### Parameters

| Parameter | Type |
| - | - |
| `shop` | [`Shop`](#shop-2) |
| `options?` | \{ `url?`: `string`; } |
| `options.url?` | `string` |

#### Returns

`Record`\<`string`, `unknown`>

***

### parseConsentCookieValue()

```ts theme={null}
function parseConsentCookieValue(raw, revision?): ConsentState;
```

Decode one cookie value. Anything that is not a well-formed decision for
the expected revision — garbage, a hand-edited value, a decision made
against an older policy — reads as undecided, which is the safe answer.

#### Parameters

| Parameter | Type |
| - | - |
| `raw` | `string` \| `null` \| `undefined` |
| `revision?` | `number` |

#### Returns

[`ConsentState`](#consentstate-1)

***

### parseCookieHeader()

```ts theme={null}
function parseCookieHeader(header): Record<string, string>;
```

Parse a `Cookie:` header (or `document.cookie`) into a plain record.

#### Parameters

| Parameter | Type |
| - | - |
| `header` | `string` \| `null` \| `undefined` |

#### Returns

`Record`\<`string`, `string`>

***

### parseEmbedFrameMessage()

```ts theme={null}
function parseEmbedFrameMessage(data): EmbedFrameMessage | null;
```

Validate a `MessageEvent.data` as a frame message, or return null.

Null for anything that is not ours, anything whose envelope version we do not
implement, and anything whose payload does not hold up — a `navigate` with no
safe URL, a `height` that is not a number. The caller has already checked the
sender's origin and window; this checks the content, and the two together are
the whole trust boundary.

#### Parameters

| Parameter | Type |
| - | - |
| `data` | `unknown` |

#### Returns

[`EmbedFrameMessage`](#embedframemessage) | `null`

***

### parsePublishableKey()

```ts theme={null}
function parsePublishableKey(key): ParsedPublishableKey;
```

Split `qb_pk_<shopPrefix>_<random>` into its parts.

The platform mints publishable keys with the shop's storage prefix in the
third segment — the same value it uses in CDN paths — never the numeric shop
id. A prefix may not contain `_` (token minting enforces this, because `_`
is the field delimiter), so it is exactly the third segment.

#### Parameters

| Parameter | Type |
| - | - |
| `key` | `string` |

#### Returns

[`ParsedPublishableKey`](#parsedpublishablekey)

***

### pickProductImage()

```ts theme={null}
function pickProductImage(product, options?): ProductImage | null;
```

One of a product's images, in display order.

`position` is the merchant's ordering and is nullable, so this sorts by it
with nulls last and falls back to the array's own order — the same ordering
the merchant sees in the admin.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | `Pick`\<[`Product`](#product-1), `"images"`> \| `null` \| `undefined` |
| `options?` | [`PickProductImageOptions`](#pickproductimageoptions) |

#### Returns

[`ProductImage`](#productimage) | `null`

***

### plainText()

```ts theme={null}
function plainText(html, maxLength?): string | undefined;
```

Strip tags and collapse whitespace, then cap at a length a search engine will
actually show.

Product descriptions are merchant-authored HTML; dropping raw markup into a
`<meta>` would put stray angle brackets in the snippet.

#### Parameters

| Parameter | Type |
| - | - |
| `html` | `string` \| `null` \| `undefined` |
| `maxLength?` | `number` |

#### Returns

`string` | `undefined`

***

### productAvailability()

```ts theme={null}
function productAvailability(product): SchemaAvailability;
```

The most optimistic availability across a product's variants.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |

#### Returns

[`SchemaAvailability`](#schemaavailability)

***

### productImages()

```ts theme={null}
function productImages(product, includePending?): ProductImage[];
```

Every renderable image of a product, in display order.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | `Pick`\<[`Product`](#product-1), `"images"`> \| `null` \| `undefined` |
| `includePending?` | `boolean` |

#### Returns

[`ProductImage`](#productimage)\[]

***

### productJsonLd()

```ts theme={null}
function productJsonLd(product, options?): Record<string, unknown>;
```

schema.org `Product`, the part that actually earns a rich result.

Price and availability go in an `Offer` for a single-priced product and an
`AggregateOffer` when the variants differ — Google reads both, and an
AggregateOffer is the honest shape for "from 99 kr". Offers are omitted
entirely when no currency is known, because `priceCurrency` is required and
guessing it would publish a wrong price.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `options?` | [`ProductJsonLdOptions`](#productjsonldoptions) |

#### Returns

`Record`\<`string`, `unknown`>

***

### purchaseDedupKey()

```ts theme={null}
function purchaseDedupKey(storeId, orderNumber): string;
```

`qb_purchase_tracked_{storeId}_{orderNumber}` — the localStorage key the
hosted storefront's success page and the hosted checkout's inline
confirmation also write, so no surface reports an order another already
reported in this browser.

#### Parameters

| Parameter | Type |
| - | - |
| `storeId` | `string` \| `number` |
| `orderNumber` | `string` \| `number` |

#### Returns

`string`

***

### purchaseEventId()

```ts theme={null}
function purchaseEventId(storeId, orderNumber): string;
```

`purchase_{storeId}_{orderNumber}` — the platform's Meta eventID for an order.

#### Parameters

| Parameter | Type |
| - | - |
| `storeId` | `string` \| `number` |
| `orderNumber` | `string` \| `number` |

#### Returns

`string`

***

### purchaseFromSession()

```ts theme={null}
function purchaseFromSession(
   snapshot, 
   orderNumber, 
   options?): PurchaseEvent | null;
```

The purchase a thank-you page reports, from the checkout session that
produced the order: its snapshotted lines and the server-authoritative
total. Null when the session carries neither (an older platform, or a
session read before the data nodes resolved) — then the page has to build
the purchase from its own data and call `trackPurchase` itself.

#### Parameters

| Parameter | Type |
| - | - |
| `snapshot` | \| `Pick`\<[`CheckoutSessionSnapshot`](#checkoutsessionsnapshot), `"data"`> \| `null` \| `undefined` |
| `orderNumber` | `string` \| `number` |
| `options?` | \{ `affiliation?`: `string` \| `null`; } |
| `options.affiliation?` | `string` \| `null` |

#### Returns

[`PurchaseEvent`](#purchaseevent) | `null`

***

### purchaseToEvent()

```ts theme={null}
function purchaseToEvent(purchase): CommerceEvent;
```

A purchase as the generic event every destination's `track` can take.

#### Parameters

| Parameter | Type |
| - | - |
| `purchase` | [`PurchaseEvent`](#purchaseevent) |

#### Returns

[`CommerceEvent`](#commerceevent)

***

### readConsentCookie()

```ts theme={null}
function readConsentCookie(cookieHeader, options?): ConsentState;
```

Read the decision from a `Cookie:` header (or `document.cookie`). The
server-side entry point: `readConsentCookie(request.headers.get("cookie"))`
in a route handler, `readConsentCookie(cookies().toString())` in Next.

#### Parameters

| Parameter | Type |
| - | - |
| `cookieHeader` | `string` \| `null` \| `undefined` |
| `options?` | [`ConsentCookieOptions`](#consentcookieoptions) |

#### Returns

[`ConsentState`](#consentstate-1)

***

### readDocumentCookie()

```ts theme={null}
function readDocumentCookie(name): string | null;
```

Read one cookie from `document.cookie`. Returns null outside a browser.

#### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |

#### Returns

`string` | `null`

***

### readGaCookies()

```ts theme={null}
function readGaCookies(cookieHeader?): GaLink;
```

#### Parameters

| Parameter | Type |
| - | - |
| `cookieHeader?` | `string` \| [`CookieList`](#cookielist) \| `null` |

#### Returns

[`GaLink`](#galink)

***

### readReturnedCheckout()

```ts theme={null}
function readReturnedCheckout(url?): ReturnedCheckout | null;
```

The session and shop the return leg put on the URL, or null when this is not
a return leg.

Reads this page's URL by default. Pass a URL — a request's, in a server
component or a route handler — to read that one instead, which is what makes
this usable before there is a `window`: the page that renders the return can
decide server-side whether to render a checkout, and with which ids.

Read-only: nothing is consumed, so it is safe to call during a render or
from a framework's router. The shop id is checked, not trusted: store ids are
integers, and a query string is writable by anyone, so anything else reads as
"not carried" rather than as a store id.

#### Parameters

| Parameter | Type |
| - | - |
| `url?` | `string` \| `URL` |

#### Returns

[`ReturnedCheckout`](#returnedcheckout) | `null`

***

### readReturnedSessionId()

```ts theme={null}
function readReturnedSessionId(): string | null;
```

The session id on this page's URL, or null. Read-only — nothing is consumed,
so it is safe to call during a render or from a framework's router.

[readReturnedCheckout](#readreturnedcheckout) returns the shop id alongside it; prefer that
when the value is going into `checkout.resume()`.

#### Returns

`string` | `null`

***

### redactKey()

```ts theme={null}
function redactKey(key): string;
```

A key with its secret portion masked, safe to put in an error message or a
log line. The shop prefix is kept: it is not a secret and it is the one part
that makes a message actionable.

#### Parameters

| Parameter | Type |
| - | - |
| `key` | `string` |

#### Returns

`string`

***

### resolveCategoryImageUrl()

```ts theme={null}
function resolveCategoryImageUrl(category, options?): string | null;
```

The `<img src>` for a category's cover image, or null when it has none.

Same shape as [resolveProductImageUrl](#resolveproductimageurl): `imageUrl` is absolute and
preferred, `image` is the bare filename kept for backwards compatibility.

#### Parameters

| Parameter | Type |
| - | - |
| `category` | \| `Pick`\<[`Category`](#category), `"image"` \| `"imageUrl"`> \| `null` \| `undefined` |
| `options?` | [`ProductImageUrlOptions`](#productimageurloptions) |

#### Returns

`string` | `null`

***

### resolveConfig()

```ts theme={null}
function resolveConfig(config): ResolvedShopkitConfig;
```

#### Parameters

| Parameter | Type |
| - | - |
| `config` | [`ShopkitConfig`](#shopkitconfig) |

#### Returns

[`ResolvedShopkitConfig`](#resolvedshopkitconfig)

***

### resolveLanguage()

```ts theme={null}
function resolveLanguage(lang?): string;
```

#### Parameters

| Parameter | Type |
| - | - |
| `lang?` | `string` \| `null` |

#### Returns

`string`

***

### resolveProductImageUrl()

```ts theme={null}
function resolveProductImageUrl(image, options?): string | null;
```

The `<img src>` for one product image, or null when there is nothing to show.

```ts theme={null}
const src = resolveProductImageUrl(product.images[0], { width: 600 })
// → https://cdn.quickbutik.com/images/ABC1/products/5c7…jpeg?auto=format&w=600&v=f1a2b3
```

Null — rather than a broken URL — is returned for an image that has no file
yet, so a caller can render its own placeholder.

#### Parameters

| Parameter | Type |
| - | - |
| `image` | [`ProductImage`](#productimage) \| `null` \| `undefined` |
| `options?` | [`ProductImageUrlOptions`](#productimageurloptions) |

#### Returns

`string` | `null`

***

### resolveStorage()

```ts theme={null}
function resolveStorage(options): StorageAdapter;
```

Turn a [StoragePreference](#storagepreference) into a concrete adapter for the runtime we
actually find ourselves in.

Never throws and never returns null: an unusable preference degrades to
memory rather than breaking the storefront. A degraded cart is a new cart —
annoying; a thrown error during SSR is a blank page.

#### Parameters

| Parameter | Type |
| - | - |
| `options` | \{ `cookieAttributes?`: [`CookieAttributes`](#cookieattributes); `cookies?`: [`CookieAccessor`](#cookieaccessor); `preference?`: [`StoragePreference`](#storagepreference); } |
| `options.cookieAttributes?` | [`CookieAttributes`](#cookieattributes) |
| `options.cookies?` | [`CookieAccessor`](#cookieaccessor) |
| `options.preference?` | [`StoragePreference`](#storagepreference) |

#### Returns

[`StorageAdapter`](#storageadapter)

***

### resolveVariant()

```ts theme={null}
function resolveVariant(product, selection): ProductVariant | null;
```

The single variant a selection identifies, or null when the selection does not
pin exactly one.

Requires every group to be chosen — a partial selection is ambiguous even if
it happens to match only one variant today, because that would make the
resolved variant depend on the catalog rather than on the shopper's choices.

A product with no option groups resolves to its single variant, which is how
a "simple" product behaves.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `selection` | [`VariantSelection`](#variantselection) |

#### Returns

[`ProductVariant`](#productvariant) | `null`

***

### searchEvent()

```ts theme={null}
function searchEvent(term): CommerceEvent;
```

#### Parameters

| Parameter | Type |
| - | - |
| `term` | `string` |

#### Returns

[`CommerceEvent`](#commerceevent)

***

### selectionForVariant()

```ts theme={null}
function selectionForVariant(product, variantId): VariantSelection;
```

The selection that identifies a given variant, for deep links and prefills.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `variantId` | `number` |

#### Returns

[`VariantSelection`](#variantselection)

***

### selectOptionValue()

```ts theme={null}
function selectOptionValue(
   product, 
   selection, 
   optionId, 
   valueId): VariantSelection;
```

Apply a click on one option value, returning the next selection.

Choosing a value that contradicts earlier choices clears the ones it
contradicts rather than refusing the click. Picking Red when XXL is selected
should give you Red with the size to re-pick — the alternative, a dead end
where nothing is clickable, is the classic variant-picker trap.

The just-clicked group is never cleared, so the shopper's most recent intent
always survives.

#### Parameters

| Parameter | Type |
| - | - |
| `product` | [`Product`](#product-1) |
| `selection` | [`VariantSelection`](#variantselection) |
| `optionId` | `number` |
| `valueId` | `number` |

#### Returns

[`VariantSelection`](#variantselection)

***

### serializeConsentState()

```ts theme={null}
function serializeConsentState(state): string;
```

The cookie value for a decided state. Throws on an undecided one: there is
nothing to store, and `clearConsentCookie` is how "undecided" is written.

#### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) |

#### Returns

`string`

***

### serializeCookie()

```ts theme={null}
function serializeCookie(
   name, 
   value, 
   attributes?): string;
```

Serialize one cookie into a `Set-Cookie` value.

#### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |
| `value` | `string` |
| `attributes?` | [`CookieAttributes`](#cookieattributes) |

#### Returns

`string`

***

### serializeJsonLd()

```ts theme={null}
function serializeJsonLd(node): string;
```

Serialize structured data for a `<script type="application/ld+json">`.

Every `<` becomes `\u003c`, which is what stops a merchant-authored product
name containing `</script>` from closing the tag and turning shop content into
executable markup. A JSON parser reads the escape identically, so nothing is
lost by being paranoid here.

#### Parameters

| Parameter | Type |
| - | - |
| `node` | `unknown` |

#### Returns

`string`

***

### storefrontRefusal()

```ts theme={null}
function storefrontRefusal(error): StorefrontRefusal | null;
```

Narrow any thrown value to a campaign storefront refusal, or `null` when it
is something else — a network error, a 404, an ordinary 409 (an out-of-stock
line, say).

```ts theme={null}
try {
  await shopkit.checkout.start({ successUrl })
} catch (error) {
  const refusal = storefrontRefusal(error)
  if (refusal?.reason === "storefront_not_live") showCampaignClosed(refusal.effectiveState)
  else if (refusal?.reason === "storefront_quantity_limit") showLimit(refusal)
  else throw error
}
```

Matches on `details.reason`, not on the HTTP status, so the decoder does not
have to know which status each reason travels on.

#### Parameters

| Parameter | Type |
| - | - |
| `error` | `unknown` |

#### Returns

[`StorefrontRefusal`](#storefrontrefusal) | `null`

***

### stripReturnedSessionId()

```ts theme={null}
function stripReturnedSessionId(): void;
```

Take the kit's two parameters off the URL, leaving every other parameter and
the hash where they were.

Stripped at all because the session it names is spent the moment it is
resumed: a shopper who reloads would otherwise re-mount a checkout for an
order they have already paid for. Stripped only once the resumed frame has
said `ready` — not on the pass that starts the resume — because a resume can
fail before the frame ever answers: a document the browser refused, a store
id nobody could supply, a handshake that timed out into the hosted checkout.
With the parameters gone, a reload of that page would find no return leg and
take the `mount()` branch; with them still there, the reload resumes again.
The platform hands the same cart the same session back either way, so
neither path can produce a second order — this is about a reload landing the
shopper back in the checkout they paid in, not on a fresh one.

`replaceState`, not `pushState` — the parameters were never a place in
anyone's history. A no-op when neither is present, so it is safe on every
`ready` a frame sends, reloads included.

#### Returns

`void`

***

### toMajorUnits()

```ts theme={null}
function toMajorUnits(amount, currency?): number;
```

Minor units → a number in major units (`9900` SEK → `99`, `990` JPY → `990`).

What GA4, Meta and every other analytics vendor want `value` and `price`
in. Rounded to the currency's own decimals so the float that comes out is
the shortest exact representation (`12.34`, never `12.340000000000002`).
Without a currency two decimals are assumed, which is right for every
currency the platform sells in by default.

#### Parameters

| Parameter | Type |
| - | - |
| `amount` | `number` |
| `currency?` | `string` \| `null` |

#### Returns

`number`

***

### toNextMetadata()

```ts theme={null}
function toNextMetadata(tags): NextLikeMetadata;
```

Adapt [SeoTags](#seotags) for a Next.js App Router `generateMetadata` export.

```ts theme={null}
export async function generateMetadata({ params }) {
  const product = await getProduct((await params).slug)
  return toNextMetadata(buildSeo({ product, ...defaults }))
}
```

**Structured data does not come along.** Next's `Metadata` has no slot for a
`<script type="application/ld+json">`, so the `jsonLd` array has to be
rendered in the page — `<JsonLd data={tags.jsonLd} />` does it. Since that is
where the rich-result value lives, it is the half not to forget; using
`<SEO />` instead emits both.

#### Parameters

| Parameter | Type |
| - | - |
| `tags` | [`SeoTags`](#seotags) |

#### Returns

[`NextLikeMetadata`](#nextlikemetadata)

***

### undecidedConsent()

```ts theme={null}
function undecidedConsent(revision?): ConsentState;
```

The state before any decision — and after a revision bump or a reset.

#### Parameters

| Parameter | Type |
| - | - |
| `revision?` | `number` |

#### Returns

[`ConsentState`](#consentstate-1)

***

### unwrapEnvelope()

```ts theme={null}
function unwrapEnvelope<T>(payload): T;
```

Strip api-core's `{ data, message, statusCode }` wrapper when present.

Detection is structural rather than per-route on purpose: the gateway unwraps
on some routes and forwards the envelope on others, and which is which has
changed over time. A payload is treated as an envelope only when it has a
`data` key alongside `statusCode`/`message` and nothing else of substance —
so a genuine resource that happens to own a `data` field is never unwrapped,
and neither is a paginated `{ data, has_more, next_cursor }` collection.

#### Type Parameters

| Type Parameter |
| - |
| `T` |

#### Parameters

| Parameter | Type |
| - | - |
| `payload` | `unknown` |

#### Returns

`T`

***

### variantAvailability()

```ts theme={null}
function variantAvailability(variant): SchemaAvailability;
```

Availability for one variant.

`stock: null` means the shop does not track inventory for it — the same signal
the variant picker treats as purchasable. Reporting OutOfStock there would
hide every product of every non-tracking shop from rich results, so null is
InStock. `preorder` wins when set, because a shopper can order it but it has
not shipped.

#### Parameters

| Parameter | Type |
| - | - |
| `variant` | [`ProductVariant`](#productvariant) |

#### Returns

[`SchemaAvailability`](#schemaavailability)

***

### variantMatchesSelection()

```ts theme={null}
function variantMatchesSelection(variant, selection): boolean;
```

Whether `variant` carries every (optionId → valueId) pair in `selection`.

#### Parameters

| Parameter | Type |
| - | - |
| `variant` | [`ProductVariant`](#productvariant) |
| `selection` | [`VariantSelection`](#variantselection) |

#### Returns

`boolean`

***

### viewItemEvent()

```ts theme={null}
function viewItemEvent(
   product, 
   variant, 
   currency?): CommerceEvent;
```

#### Parameters

| Parameter | Type |
| - | - |
| `product` | `Pick`\<[`Product`](#product-1), `"id"` \| `"name"` \| `"price"`> |
| `variant` | \| `Pick`\<[`ProductVariant`](#productvariant), `"id"` \| `"price"` \| `"options"`> \| `null` \| `undefined` |
| `currency?` | `string` \| `null` |

#### Returns

[`CommerceEvent`](#commerceevent)

***

### viewItemListEvent()

```ts theme={null}
function viewItemListEvent(items, listName?): CommerceEvent;
```

#### Parameters

| Parameter | Type |
| - | - |
| `items` | [`CommerceItem`](#commerceitem)\[] |
| `listName?` | `string` |

#### Returns

[`CommerceEvent`](#commerceevent)

***

### writeConsentCookie()

```ts theme={null}
function writeConsentCookie(state, options?): void;
```

Write the decision to `document.cookie`. No-op outside a browser.

#### Parameters

| Parameter | Type |
| - | - |
| `state` | [`ConsentState`](#consentstate-1) |
| `options?` | [`ConsentCookieWriteOptions`](#consentcookiewriteoptions) |

#### Returns

`void`

***

### writeDocumentCookie()

```ts theme={null}
function writeDocumentCookie(
   name, 
   value, 
   attributes?): void;
```

Write one cookie to `document.cookie`. No-op outside a browser.

#### Parameters

| Parameter | Type |
| - | - |
| `name` | `string` |
| `value` | `string` |
| `attributes?` | [`CookieAttributes`](#cookieattributes) |

#### Returns

`void`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.