Integration guide for LLMs
This page is for a coding assistant (LLM) implementing the Headless SDK for a merchant, and for the engineer who reviews that code. It covers the facts to confirm before integration, which interface is authoritative, the order in which generated code must run, how to read failures, and how to accept the work from observed browser and cart results. Method signatures, types, and common errors live in the SDK Reference. This page does not repeat them.
How to use this guide
Section titled “How to use this guide”Read Before you integrate and Quick start first, then split the work into verifiable units. Change one unit at a time and observe the result in the browser before moving on. Hand the task brief below to the coding assistant with the site facts filled in. Replace every placeholder in angle brackets.
Implement the Xcotton Headless SDK integration.Documentation origin: <docs-origin>Storefront repository: <repository>Framework and rendering: <Next.js App Router / Remix / Nuxt / SvelteKit / other>Active market: country=<US>, currency=<USD>, language=<en>Store capabilities: SP=<on or off>, PP=<on or off>, SP integration mode=<cart / checkout / checkoutPlus (written checkout+ in these docs), or "read from getMode()">Cart reads and writes: <Shopify Storefront API / storefront BFF>Source of an existing cart ID: <initial query, post-login merge, server injection>Site-owned functions: <reloadCartView, main product write, checkout redirect, error display>Read these documentation paths first:- /en/v2/platform-integration/shopify/headless-sdk-prepare/- /en/v2/platform-integration/shopify/headless-sdk/- /en/v2/platform-integration/shopify/headless-sdk-cart-sync/- /en/v2/platform-integration/shopify/headless-sdk-modes/- /en/v2/platform-integration/shopify/headless-sdk-api/
Constraints:- Call only public window.xcotton methods. Do not write fee lines or protection lines directly.- Call init once per page load. After a standalone site cart write, await syncCart. The pp.addToCart callback only writes the main product and returns its identity; the SDK performs the sync.- Do not implement or rewrite SDK internals, do not invent site functions, and do not use mock data in the delivered code.- Submit only steps you actually verified, and list what you could not verify.
Deliver: script loading and initialization, the SP checkout path, the PP product-page and cart paths for enabled capabilities, and failure handling.Acceptance: follow the Acceptance section of "Integration guide for LLMs" and observe each item in the browser.A coding assistant must not infer interfaces from older versions, historical reports, or internal comments. Use the SDK Reference and the delivered xcotton-types.ts as the source for public methods. Read the runtime integration mode, quote, and cart add-protection candidates with sp.getMode(), sp.getQuote(), and pp.getCartQuotes().
Confirm before you start
Section titled “Confirm before you start”| Fact to confirm | Where to get it | What it changes in the code |
|---|---|---|
| Store domain and Storefront Public Access Token | Merchant back office, site environment variables | Fill shop and publicAccessToken in window.xcottonConfig. The token must read and write the cart |
Active market country, currency, and language | Site market resolution (country, language) and cart cost.totalAmount.currencyCode (for currency) | The three values come from the active site market. Only currency must match the cart currency, while language affects UI copy only. A market switch needs a new page load and new config |
| Whether SP and PP are enabled | Merchant back-office configuration.window.xcotton.pp can be checked after init, while window.xcotton.sp requires the first cart bind and sync | Do not generate code for a disabled capability. A missing sp on a cart-less startup does not mean SP is off |
| SP integration mode | Call sp.getMode() after the first cart sync succeeds and sp is present | Decides whether the checkout button calls resolveSP() or addSpFee() and declineSpFee() |
| Cart read and write path | Site architecture and existing interfaces | Decides between the default adapter and a custom adapter |
| When a cart ID becomes available | Site cart query, post-login merge logic | Pass it to init({ cartId }) when known at startup. Use syncCart(cartId) when it arrives later |
| Site-owned functions | Storefront repository | reloadCartView(), the main product write, checkout redirect, and error display are site code. The coding assistant must not invent implementations |
| Where the panel containers live | Page layout | The container selector must match both xcottonConfig and the page. Containers may mount late, and the SDK attaches a panel when the node appears, including for drawers and SPA route changes |
| Acceptance environment | Test store, market, test products | Real quotes and cart writes need a working store. Do not run destructive tests on a production store |
If a fact cannot be confirmed from code or from the merchant, list it as open instead of guessing a default. country, currency, and language come from the site for the active market and are sent with the configuration request. PP quotes use them too. The SDK currency guard checks the cart and its money fields only. A country mismatch with the buyer country is not blocked by the SDK, but it makes the configuration and quote belong to the wrong market. language affects UI copy only and does not change the settlement currency.
Implementation order
Section titled “Implementation order”The order is fixed: make the SDK available, make cart synchronization correct, then connect SP and PP. Every step needs an observable result.
- Declare config, then load the script.
window.xcottonConfigmust exist beforesdk.jsruns. Under SSR, render the config and script tag for the active market without touchingwindowon the server, and run the script before framework hydration. Checktypeof window.xcotton?.init === 'function'. - Place the containers. Put SP containers on the cart page and in the cart drawer, and the PP container on the product page. The selectors must match the config. Check that
document.querySelector(selector)matches and that the SDK renders a panel inside. - Initialize once. Call
await window.xcotton.init(options)once per page load. SupplycartId,adapter, andonCartChangeinoptionsas needed. PasscartIdwhen a cart already exists. Omit it otherwise. Check that the Promise resolves, thatwindow.xcotton.ppexists when PP is enabled, and thatwindow.xcotton.spexists when SP is enabled and the first cart sync succeeds. Namespace presence alone does not prove synchronization succeeded. - Synchronize site cart writes. After a standalone site add to cart, quantity change, removal, clear, or cart switch, call
await window.xcotton.syncCart(cart.id). Disable checkout and the next cart write until it completes.pp.addToCartincludes synchronization. Do not callsyncCartfrom its main-product callback or from site events triggered by that callback. Check that the returnedCartmatches the page and thatonCartChangeonly refreshes the view. - Connect SP by mode. For
cartandcheckout, callawait sp.resolveSP()before navigation. Forcheckout+, the two checkout buttons callawait sp.addSpFee()andawait sp.declineSpFee(). Never navigate after a rejection. Check that the fee line appears or disappears and that the checkout total agrees. - Connect PP by scenario. On a product variant change call
await pp.loadPlans(variantId). When leaving the product page callawait pp.loadPlans(null). Add to cart withpp.addToCart({ addMainProduct }). For cart add-protection, readgetCartQuotes()first, then callofferProtection()oraddProtection(). Remove withremoveProtection({ mainLineId }). Check that the main product is written once and that the protection quantity equals the total quantity of protected main lines with the same variant and plan. - Close the failure paths. Reload the page after an environment initialization failure, recover a failed first sync with
syncCart(cartId), wait out operation conflicts, and read the cart before retrying an unknown write. Check that a failure neither navigates nor repeats a write nor retries automatically.
Under an SSR framework, the server renders the config and script tag for the active market, and the SDK still runs in the browser. Containers may mount after the page renders. The SDK observes the node and attaches a panel, and a drawer that opens later or an SPA route change recovers through the same mechanism. A full page reload clears window and needs a new init. SPA route changes keep the initialized namespaces. Call loadPlans(null) when leaving the product page instead of relying on unmount.
Cart read and write path
Section titled “Cart read and write path”The default adapter calls the Shopify Storefront API and fits most storefronts. Implement XcottonCartAdapter only when cart reads and writes must pass through a storefront BFF, server authentication, or risk checks. Choose from the site’s existing interfaces, not from preference.
| Scenario | Choice | Delivery requirement |
|---|---|---|
| The site already has a Storefront token and uses the Storefront API for the cart | Default adapter | Do not pass adapter. Do not wrap an empty implementation |
| All reads and writes must pass through a BFF | Custom adapter | Implement getCart, addLines, updateLines, removeLines, and updateAttributes. Return a complete Cart |
| Only some methods pass through a BFF | Partial override | Omitted methods fall back to the default implementation. Both paths must use the same cart and line IDs. If line IDs differ, implement all methods |
A custom adapter must meet these requirements:
- Return the complete
Cartafter each operation, including every page and the quote fields, withlines.pageInfo.hasNextPageset tofalse. - Preserve
cart.id, line IDs, amounts, and currency codes. Do not convert currency or replace a zero amount with another value. - Reject on network, authentication, and rate-limit failures. Do not turn a failure into
null. For a rate-limit error, make at least one ofstatus: 429,code: 'THROTTLED', or amessagecontaining429/THROTTLEDhold, and addretryAfterMswhen known. Without it the SDK uses a 30-second cooldown. - For line attributes, omit the array or pass
nullto keep existing values, pass[]to clear them, and pass a non-empty array to replace them.updateAttributesreceives the complete attribute array. - Do not swallow errors, retry automatically, or invent line IDs. Test doubles belong in tests, not in the storefront runtime path.
A coding assistant must not generate an adapter that returns fake data to make the page look usable. Use the default adapter when the site has no BFF.
SP and PP paths
Section titled “SP and PP paths”The SP entry point depends on the integration mode. Read the mode with sp.getMode(). Do not infer it from page controls or older versions. The return value 'checkoutPlus' is the checkout+ mode in these docs. See Integration modes, Checkout mode, and Checkout+ mode for the differences.
| Customer action | Call | Notes |
|---|---|---|
Cart panel switch (cart) | SDK writes the cart internally | The site does not write the fee line. Still call resolveSP() before checkout |
Checkout click (cart, checkout) | await sp.resolveSP() | The result’s action is added, removed, or skipped. Do not navigate after a rejection |
Checkout with protection (checkout+) | await sp.addSpFee() | Resolves { added: true, extId }. Rejects when the cart is not insurable |
Checkout without protection (checkout+) | await sp.declineSpFee() | Clears the fee and writes the refusal marker. Do not navigate directly |
| Display the protection price | await sp.getQuote() | Read-only. A zero-amount quote is valid, while an uninsurable cart rejects |
| Open the coverage details | await sp.showModal() | Requires a mounted SP panel |
Do not call resolveSP() in checkout+. It has no switch intent and rejects with INVALID_ARGUMENT. Do not read or write fee lines yourself, and do not write the same protection again through the OpenAPI.
PP has a product-page part and a cart part. Product-page quoting and selection do not need a cart. Writing a protection line needs a synchronized cart. See Add product protection and Cart add-protection button for the detailed flows.
- Product page:
const { context, options } = await pp.loadPlans(variantId).optionsare the submittable candidates from this quote.contextmay be omitted to use the global context captured at call start. When passed explicitly, it must not be constructed by hand or reused across products. CallloadPlans(null)when leaving the product page. - Add to cart:
await pp.addToCart({ addMainProduct }). The callback writes only the main product, receives(attributes, context), and returns{ cartId, mainLineId }. Do not call SDK root methods that take cart permission inside the callback (such asinit,syncCart, orpp.addToCart), and do not read the page’s current variant. If the customer cancels the plan modal, the main product is still added and the result hasprotection: 'skipped'. - Manual chain:
getSelectedPlan(), the site’s main product write,syncCart(cartId), thenaddProtection({ mainLineId, selection }). Use it oraddToCart(), not both in one flow. - Cart add-protection: call
await pp.getCartQuotes()and match rows bymainLineId.offerProtection({ mainLineId })opens the SDK modal, including when only one plan exists.addProtection({ mainLineId, selection })submits an original option from a site-drawn list. Remove protection withremoveProtection({ mainLineId }), passing the main product line ID, not the protection line ID.
Do not link rows by product name or plan name, do not derive mainLineId from mainVariantId, and do not assemble PPSelection or write _PlanID yourself. A submittable PPSelection comes from a current SDK quote or selection result: the options of loadPlans() and getCartQuotes(), or the non-null return value of getSelectedPlan() and showModal(). See the SDK Reference for the full rules.
Errors and write outcomes
Section titled “Errors and write outcomes”A public SDK method rejects with an XcottonError, including SDK_NOT_INITIALIZED before init. Site functions, a failed sdk.js load, or a missing window.xcotton produce ordinary exceptions that this guard does not cover. Check window.xcotton first, narrow SDK errors with window.xcotton.isXcottonError(error) and branch on code, and handle other errors as ordinary exceptions. Use message for logs only and never branch on its text.
| Code | Meaning | Handling |
|---|---|---|
SDK_NOT_INITIALIZED | The SDK environment is not ready | Call init once if it has not started. Otherwise await the shared initialization Promise and handle its failure |
SDK_INIT_IN_PROGRESS | init() is still running | Do not call init again. Wait for the first call’s Promise, which the site should share |
SDK_ALREADY_INITIALIZED | init() already finished or failed. The phase can be ready, environment_failed, sync_failed, or busy | Do not call init again. Reload the page or use syncCart according to the phase |
OPERATION_IN_PROGRESS | Another cart operation is running | Wait for it to settle, then decide whether to synchronize |
CART_NOT_BOUND | The first synchronization has not completed | Get the cart, then call syncCart(cartId) |
CART_CURRENCY_MISMATCH | The cart currency differs from the configured currency | Switch to the cart and configuration for the active market |
PP_CONTEXT_CHANGED | The product context changed or the quote was superseded | Use the latest loadPlans() result. Do not retry the old call |
PP_SELECTION_IN_PROGRESS | Another PP selection or purchase flow is running | Wait for the active flow to settle |
PP_NO_OPTIONS | No plan is available, or the cart add-protection switch is off | Do not open a modal or write the cart. Hide the entry point |
PP_ALREADY_PROTECTED | The main line already has a real protection line | Refresh the cart and hide the add-protection entry |
SP_NOT_AVAILABLE | The cart is not insurable. The reason is in context.reason | Do not navigate. Follow recovery to adjust the cart, or continue without protection |
SP_COVERAGE_LIMIT_EXCEEDED | The cart total exceeds the coverage limit | Do not navigate. Follow recovery to reduce the total, or continue without protection |
SP_PANEL_REQUIRED | No SP panel is mounted | Mount the container before calling showModal() |
ADAPTER_RATE_LIMITED | Cart requests are in a cooldown | Wait for context.retryAfterMs. Use 30 seconds when the field is absent |
A failed cart write may still have taken effect. Read context.writeOutcome: not_sent means the request was not sent, not_applied means an explicit rejection with no effect, partial means part of the write is confirmed, and unknown means the write result cannot be confirmed. Do not retry automatically after partial or unknown. Read the cart first. PP_WRITE_CONFIRMATION_FAILED from pp.addToCart also carries stage, mainProduct, protection, and writeOutcome. mainProduct can be added, or a write outcome such as not_sent or unknown. Do not present unknown as added, and do not present a callback that never ran as a successful main product write.
Checkout depends only on the Promise of the corresponding API. onCartChange reports confirmed facts and is not a success signal. It can still run after a failure when a read-back confirms the cart. A successful cart-less init() confirms environment readiness. A successful init with cartId also includes the first synchronization of that cart.
Use only the returned code, context, and an available cause. Do not infer a more specific upstream reason, write result, or callback state, and do not log or display one as sent or successful when the SDK did not confirm it.
Acceptance
Section titled “Acceptance”Accept from observed results: cart lines, Network requests, the Promise code and context, and navigation. A successful compile, a clean type check, or a screenshot that renders does not prove the integration works.
| Check | Action | Pass condition |
|---|---|---|
| Loading | Open the page and inspect Network and Console | sdk.js loads.window.xcotton.init is available. No unhandled errors |
| Initialization | Open the page and mount several components | init runs once. Repeated component mounts share the same Promise and do not each call init.sdk.js loads once |
| Namespaces | Check window.xcotton.pp when PP is enabled and window.xcotton.sp when SP is enabled and a cart exists | pp exists after init.sp exists after the first sync with a cart ID. A missing sp on a cart-less startup does not mean SP is off, so use the back-office configuration |
| Synchronization | Add, change quantity, remove, and clear from the site | Each standalone write is followed by await syncCart(cart.id). The SDK handles synchronization inside pp.addToCart. The returned Cart matches the page.onCartChange does not start a sync loop |
SP cart, checkout | Toggle the switch, click checkout | The fee line follows the intent. Navigation happens only after resolveSP() resolves. A rejection keeps the page |
SP checkout+ | Click both checkout buttons | Checkout with protection writes the fee line. Checkout without protection removes it and writes the refusal marker.resolveSP() is not called |
| PP product page | Switch variants, select a plan, add to cart | The panel shows only the latest variant. The main product is written once. The protection quantity equals the total quantity of protected main lines with the same variant and plan. Cancelling the modal still adds the main product with protection: 'skipped' |
| PP cart | Read candidates, add protection, remove it | Candidates match by mainLineId. A single plan still opens the modal. Cancelling writes nothing. The main product remains after removal |
| Failure paths | Simulate a network failure, throttling, and a context switch | No navigation and no repeated write. Read the cart first when writeOutcome is partial or unknown. No retry during the cooldown |
| Viewports | Repeat at desktop and mobile widths | Panels, buttons, and modals remain operable. Containers mount on both the page and the drawer |
Finally, record the unverified items and limits in the delivery notes. Verify real cart writes only in a test store. Do not clear carts, repeat purchases, or remove protection in a production store.