What the kit remembers
Four opaque handles and one preference, never prices, personal data or credentials:
The default prefix is
qb (qb_cart_id, …). The first four are server-side handles, so a leaked or stale value costs at most a fresh cart, and “just clear it” is always a valid recovery. The currency is a display preference, validated on every read so a tampered value is ignored rather than sent. Because it is a cookie by default, a server render with request-cookie storage prices the page in the currency the browser picked. See Currencies.
The shopper’s cookie consent decision is a separate cookie, qb_consent, owned by the consent layer rather than the storage adapter. See Consent and analytics.
The shop id is stored twice because the copies expire on different clocks: the platform drops the cart the moment an order exists, but the thank-you page and the embedded checkout’s return leg still need the id. checkout.finalize() forgets the cart and the session; only the cart TTL forgets the shop id.
Picking an adapter
storage accepts "auto" (default), "cookie", "localStorage", "memory" or your own adapter. "auto" resolves like this:
1
A cookies accessor was supplied
Server cookie storage. Supplying one always wins.
2
Not a browser
Memory.
3
document.cookie is usable
Document cookies.
4
localStorage is usable
localStorage.5
Otherwise
Memory.
localStorage on purpose. A cookie is the only client-side store the server can also read, which is what lets a server render see the same cart as the browser.
Resolution never throws. An unusable preference degrades to memory, because a new cart is better than a blank server render. Check shopkit.runtime.persistent (false means memory) when a cart does not survive a reload.
Server-side cookies
Supply aCookieAccessor. Every method may return a promise.
Next.js App Router
A server component cannot set cookies and a server action can, so use two clients:lib/shopkit.ts
Framework-free: createRequestCookieStorage
Reads cookies off a Request, a Headers object or a raw cookie header string, and optionally collects writes as Set-Cookie strings. The fit for Astro endpoints, TanStack Start, Remix loaders, Hono, Express and Workers:
req.headers.cookie as the source and res.append("Set-Cookie", cookie) for each collected string. Pass secure: true explicitly on a server: there is no ambient location to infer https from.
withStorage: one config, many requests
withStorage(adapter) returns a copy of the client bound to different storage. It is the idiomatic way to reuse one configuration across requests, each with its own cookie jar:
cookies accessor, so the adapter you pass is the one used.
Server-rendered cart without a flash
Read the cart on the server and hand it to the browser layer, so the first paint already shows the real basket:Cookie attributes
An empty stored value is treated as absent everywhere.
Bring your own adapter
Any object withkind, get, set and remove works, and each may return a promise. Useful for a signed cookie, a Redis or KV-backed session, or a test double:
ttlSeconds.
Exported helpers
isBrowser() requires a real window and document, so a web worker is not a browser here. hasLocalStorage() probes with a real write, because Safari private mode exposes localStorage and throws on use.
See Utilities for signatures.