Skip to content

Product protection modal

When a customer clicks “Add to cart” without selecting a plan in the PP panel, the SDK can present a modal prompting them to choose a plan. If they dismiss the modal, the main product is still added to the cart normally. The modal does not require a cart. It opens on the product page whenever plans are available.

You can trigger the modal in two ways:

TriggerMethodControlled by your account manager (AM)‘s setting
Automatically on add to cartpp.addToCart()Yes. When disabled in the back office, no plan means protection: 'skipped'
Explicitly from a custom buttonpp.showModal()No. Not controlled by the back-office modal setting

A standard add-to-cart needs no modal branch at all: pp.addToCart() already resolves the selection and opens the modal when configured, resolving protection: 'skipped' if the customer cancels. Use pp.showModal() only when you want to invite the customer to pick a plan earlier.

  • You have implemented the add-to-cart flow in Add product protection. This page explains how the modal fits into that flow. The add-to-cart call itself does not change.
  • The product page has called loadPlans(variantId) for the currently selected variant.
  • The custom button needs a storefront function such as showSelectedPlan(plan) to display the chosen plan.
  1. Standard add to cart: call pp.addToCart({ addMainProduct }) directly. The callback is (attributes, context) and no variantId is passed. The SDK awaits the quote for this variant: a plan already selected in the panel is used immediately without a modal. Otherwise the back-office setting decides whether to open the modal.
  2. Handle the result: if the customer dismisses the modal, the result still resolves with protection: 'skipped', and the main product is added without a protection line. All other results match Add product protection.
  3. Custom trigger button: to open the modal from a dedicated “Choose protection plan” button, call pp.showModal() (or pp.showModal(context)). Confirming resolves with PPSelection. Cancelling, closing, or pressing Escape resolves with null and clears the PDP selection. No available plan rejects with PP_NO_OPTIONS. Switching variants or leaving the page while waiting or while the modal is open rejects with PP_CONTEXT_CHANGED and is not treated as a cancellation.
  4. Reuse the chosen plan: a plan confirmed in the modal stays in the panel state, so the next pp.addToCart() resolves the same selection. Your storefront neither stores the choice nor rewrites the main product attributes.

Both entry points capture the product context when called. Switching variants or leaving the page while waiting for a quote (including A→B→A) rejects with PP_CONTEXT_CHANGED. Any re-quote after the modal opens closes it and rejects the pending call with PP_CONTEXT_CHANGED, so the customer can never confirm a stale price. Cancellation and technical failure stay separate: only a customer cancellation resolves null and clears the PDP selection.

Selection exclusivity starts with waiting for the quote and ends on confirm, cancel, or failure. Calling showModal() or setSelectedPlan() while it runs rejects immediately with PP_SELECTION_IN_PROGRESS (no queueing, no stacked modals). After a cancellation, the next add to cart still follows the back-office automatic modal setting. The returned selection is an independent snapshot, and mainLineAttributes is already a Storefront AttributeInput[], so no format conversion is needed.

The example below uses pp.showModal() to open the modal early from a custom button. If you only need “open the modal on add to cart when configured”, skip the custom button and use pp.addToCart() from the integration page:

components/choose-plan-button.tsx
'use client'
import { xcottonInit } from '@/lib/xcotton'
import { showSelectedPlan } from '@/lib/cart'
async function onChoosePlanClick() {
await xcottonInit()
const pp = window.xcotton?.pp
if (!pp) throw new Error('[XC] PP is not enabled for this store')
// Show the chosen plan; the next pp.addToCart resolves the same selection.
const selection = await pp.showModal()
if (selection) showSelectedPlan(selection.plan)
}
export function ChoosePlanButton() {
return (
<button
type="button"
onClick={() => void onChoosePlanClick().catch(console.error)}
>
Choose protection plan
</button>
)
}
ActionExpected result
Select a plan in the panel, then click Add to cartNo modal opens. The product and protection line are added directly
Click Add to cart without a plan (modal setting enabled)The plan selection modal opens
Dismiss, cancel, or press Escape on the add-to-cart modalpp.addToCart() still adds the main product with protection: 'skipped'. The PDP selection is cleared
Dismiss, cancel, or press Escape on a custom showModal()Resolves null, clears the PDP selection, and writes nothing
Add to cart again after cancelling (still no PDP plan)The back-office setting decides again. The cancellation is not recorded as a refusal
Confirm a plan from a custom showModal() buttonA selection is returned and the next pp.addToCart uses that same plan
Switch variants or leave the page while the modal is openThe modal closes and the pending call rejects PP_CONTEXT_CHANGED. The newer context keeps its selection
Call showModal() again while the modal is openRejects PP_SELECTION_IN_PROGRESS. No second modal is stacked
SymptomCause and fix
Modal does not open when adding to cart without a planYour account manager (AM) disabled the automatic modal on add to cart. Ask them to enable it, or call showModal() from a custom button
The pending showModal() rejected with PP_CONTEXT_CHANGEDThe variant changed or loadPlans(null) ran while the modal was open, voiding the previous quote. Reopen the modal for the active variant
The custom button does nothingwindow.xcotton.pp is undefined: PP is disabled for the store or init() has not resolved. Neither depends on a cart