Skip to content

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.

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().

Fact to confirmWhere to get itWhat it changes in the code
Store domain and Storefront Public Access TokenMerchant back office, site environment variablesFill shop and publicAccessToken in window.xcottonConfig. The token must read and write the cart
Active market country, currency, and languageSite 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 enabledMerchant back-office configuration.window.xcotton.pp can be checked after init, while window.xcotton.sp requires the first cart bind and syncDo not generate code for a disabled capability. A missing sp on a cart-less startup does not mean SP is off
SP integration modeCall sp.getMode() after the first cart sync succeeds and sp is presentDecides whether the checkout button calls resolveSP() or addSpFee() and declineSpFee()
Cart read and write pathSite architecture and existing interfacesDecides between the default adapter and a custom adapter
When a cart ID becomes availableSite cart query, post-login merge logicPass it to init({ cartId }) when known at startup. Use syncCart(cartId) when it arrives later
Site-owned functionsStorefront repositoryreloadCartView(), the main product write, checkout redirect, and error display are site code. The coding assistant must not invent implementations
Where the panel containers livePage layoutThe 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 environmentTest store, market, test productsReal 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.

The order is fixed: make the SDK available, make cart synchronization correct, then connect SP and PP. Every step needs an observable result.

  1. Declare config, then load the script. window.xcottonConfig must exist before sdk.js runs. Under SSR, render the config and script tag for the active market without touching window on the server, and run the script before framework hydration. Check typeof window.xcotton?.init === 'function'.
  2. 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.
  3. Initialize once. Call await window.xcotton.init(options) once per page load. Supply cartId, adapter, and onCartChange in options as needed. Pass cartId when a cart already exists. Omit it otherwise. Check that the Promise resolves, that window.xcotton.pp exists when PP is enabled, and that window.xcotton.sp exists when SP is enabled and the first cart sync succeeds. Namespace presence alone does not prove synchronization succeeded.
  4. 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.addToCart includes synchronization. Do not call syncCart from its main-product callback or from site events triggered by that callback. Check that the returned Cart matches the page and that onCartChange only refreshes the view.
  5. Connect SP by mode. For cart and checkout, call await sp.resolveSP() before navigation. For checkout+, the two checkout buttons call await sp.addSpFee() and await sp.declineSpFee(). Never navigate after a rejection. Check that the fee line appears or disappears and that the checkout total agrees.
  6. Connect PP by scenario. On a product variant change call await pp.loadPlans(variantId). When leaving the product page call await pp.loadPlans(null). Add to cart with pp.addToCart({ addMainProduct }). For cart add-protection, read getCartQuotes() first, then call offerProtection() or addProtection(). Remove with removeProtection({ 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.
  7. 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.

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.

ScenarioChoiceDelivery requirement
The site already has a Storefront token and uses the Storefront API for the cartDefault adapterDo not pass adapter. Do not wrap an empty implementation
All reads and writes must pass through a BFFCustom adapterImplement getCart, addLines, updateLines, removeLines, and updateAttributes. Return a complete Cart
Only some methods pass through a BFFPartial overrideOmitted 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 Cart after each operation, including every page and the quote fields, with lines.pageInfo.hasNextPage set to false.
  • 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 of status: 429, code: 'THROTTLED', or a message containing 429 / THROTTLED hold, and add retryAfterMs when known. Without it the SDK uses a 30-second cooldown.
  • For line attributes, omit the array or pass null to keep existing values, pass [] to clear them, and pass a non-empty array to replace them. updateAttributes receives 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.

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 actionCallNotes
Cart panel switch (cart)SDK writes the cart internallyThe 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 priceawait sp.getQuote()Read-only. A zero-amount quote is valid, while an uninsurable cart rejects
Open the coverage detailsawait 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). options are the submittable candidates from this quote. context may 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. Call loadPlans(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 as init, syncCart, or pp.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 has protection: 'skipped'.
  • Manual chain: getSelectedPlan(), the site’s main product write, syncCart(cartId), then addProtection({ mainLineId, selection }). Use it or addToCart(), not both in one flow.
  • Cart add-protection: call await pp.getCartQuotes() and match rows by mainLineId. 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 with removeProtection({ 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.

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.

CodeMeaningHandling
SDK_NOT_INITIALIZEDThe SDK environment is not readyCall init once if it has not started. Otherwise await the shared initialization Promise and handle its failure
SDK_INIT_IN_PROGRESSinit() is still runningDo not call init again. Wait for the first call’s Promise, which the site should share
SDK_ALREADY_INITIALIZEDinit() already finished or failed. The phase can be ready, environment_failed, sync_failed, or busyDo not call init again. Reload the page or use syncCart according to the phase
OPERATION_IN_PROGRESSAnother cart operation is runningWait for it to settle, then decide whether to synchronize
CART_NOT_BOUNDThe first synchronization has not completedGet the cart, then call syncCart(cartId)
CART_CURRENCY_MISMATCHThe cart currency differs from the configured currencySwitch to the cart and configuration for the active market
PP_CONTEXT_CHANGEDThe product context changed or the quote was supersededUse the latest loadPlans() result. Do not retry the old call
PP_SELECTION_IN_PROGRESSAnother PP selection or purchase flow is runningWait for the active flow to settle
PP_NO_OPTIONSNo plan is available, or the cart add-protection switch is offDo not open a modal or write the cart. Hide the entry point
PP_ALREADY_PROTECTEDThe main line already has a real protection lineRefresh the cart and hide the add-protection entry
SP_NOT_AVAILABLEThe cart is not insurable. The reason is in context.reasonDo not navigate. Follow recovery to adjust the cart, or continue without protection
SP_COVERAGE_LIMIT_EXCEEDEDThe cart total exceeds the coverage limitDo not navigate. Follow recovery to reduce the total, or continue without protection
SP_PANEL_REQUIREDNo SP panel is mountedMount the container before calling showModal()
ADAPTER_RATE_LIMITEDCart requests are in a cooldownWait 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.

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.

CheckActionPass condition
LoadingOpen the page and inspect Network and Consolesdk.js loads.window.xcotton.init is available. No unhandled errors
InitializationOpen the page and mount several componentsinit runs once. Repeated component mounts share the same Promise and do not each call init.sdk.js loads once
NamespacesCheck window.xcotton.pp when PP is enabled and window.xcotton.sp when SP is enabled and a cart existspp 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
SynchronizationAdd, change quantity, remove, and clear from the siteEach 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, checkoutToggle the switch, click checkoutThe fee line follows the intent. Navigation happens only after resolveSP() resolves. A rejection keeps the page
SP checkout+Click both checkout buttonsCheckout with protection writes the fee line. Checkout without protection removes it and writes the refusal marker.resolveSP() is not called
PP product pageSwitch variants, select a plan, add to cartThe 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 cartRead candidates, add protection, remove itCandidates match by mainLineId. A single plan still opens the modal. Cancelling writes nothing. The main product remains after removal
Failure pathsSimulate a network failure, throttling, and a context switchNo navigation and no repeated write. Read the cart first when writeOutcome is partial or unknown. No retry during the cooldown
ViewportsRepeat at desktop and mobile widthsPanels, 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.