# Cart Overview

> How Cart V2 profiles work, how the renderer resolves and opens them, and which MCP action does what.

Canonical URL: https://77865786950d:3005/cart

Cart profiles are reusable, versioned cart experiences. A store can keep
multiple profiles for different merchandising strategies, publish stable
versions, choose a store default, and assign a profile to a page or campaign.

Open [Storefront → Cart](https://app.trylexsis.com/storefront/settings/cart) to create, preview,
publish, assign, and review cart profiles in the app.

| Page | Covers |
|---|---|
| [Profiles and Edit Loop](/cart/profiles) | Read, create, edit, preview, publish, roll back, assign and archive profiles |
| [Cart Styling](/cart/styling) | Design contract, presets, tokens, custom CSS and every stable `data-part` |
| [Custom Cart Modules](/cart/custom-modules) | Section source inside a profile: cart state, commands, conditions and bindings |
| [Section scripting: cart](/scripting/cart) | `lifecycle.cart`, `lifecycle.onCart` and `lifecycle.setVisible` reference |

## Page and cart ownership

The page and cart are separate:

- Page sections own the storefront content.
- Cart V2 is enabled by default and cannot be disabled.
- A cart profile owns the drawer HTML, module order, rules, offers, behavior,
  responsive mode, and cart-only styling.
- The renderer resolves and injects the cart after the page sections.
- Page titles, SEO metadata, and page section HTML never select a cart profile.

Do not add `DrawerShell`, `CartLines`, or other cart islands to page source.
Publish validation rejects an inline cart. New page source does not need a
cart flag; the compiler still accepts `use_cart_v2:true` on older pages and
rejects `use_cart_v2:false`. Older pages that still use the deprecated
`CartDrawer` island are listed in [Deprecated Islands](/islands/deprecated).

## Resolution order

The renderer selects one published profile for every cart-profile page:

```text
page assignment > campaign assignment > store default > legacy fallback
```

A page assignment is the most specific. Removing it restores the next
available fallback instead of disabling the cart.

Only published, non-archived profiles can become defaults or assignments.
Editing a profile creates draft changes. The live cart remains on its current
published snapshot until the merchant publishes through MCP or from
[Storefront → Cart](https://app.trylexsis.com/storefront/settings/cart).

Changing an assignment, default, published profile version, or archive state
automatically refreshes the affected published Shopify pages after
the database change commits. You do not need to republish each page manually.

## Missing profile behavior

A commerce page with purchase controls but no effective profile is invalid.
Preview marks the page with `data-lx-cart-profile-state="missing"` and reports:

```text
This preview has purchase controls but no effective Cart V2 profile.
```

Fix the store default or page/campaign assignment before publishing. Do not
work around the warning by manually embedding DrawerShell or cart child
islands in page source.

## How a cart opens

Cart triggers and cart presentation communicate through the shared
`cart:open` event:

1. The renderer injects the resolved profile's `DrawerShell` after the page
   sections.
2. `BuyBox`, `QuickAdd`, `StickyBar`, product cards, and offer cards add the
   selected variant through the shared cart action.
3. The purchase control shows its own feedback while the mutation runs.
4. The renderer defaults connected stores to `optimistic-v1` without rollout
   flags or a store allowlist. In this runtime, acceptance emits
   `cart:open` immediately and projects the requested items into shared state.
   Existing experiment responses and captured legacy artifacts retain their
   existing behavior.
5. The storefront hydrator bridges a DOM `cart:open` event to the shared cart
   event bus.
6. The injected `DrawerShell` listens for `cart:open`, opens, and hydrates its
   child cart islands on first use.
7. Shopify responses reconcile the cart. Rejected changes roll back while
   accepted items remain. Errors appear in the drawer and purchase control;
   an open drawer does not itself mean Shopify confirmed the add.

The optimistic runtime accepts quantity edits and removals while Shopify is
busy. Its subtotal is explicitly estimated until confirmation; discounts and
the final total update after Shopify and offers settle. Checkout waits for the
latest cart and then navigates automatically, with a cancel action while waiting.
Custom sections must not add their own Shopify requests, optimistic totals,
retry loops, or delayed drawer-opening logic.

The site header cart button uses the same `cart:open` event. A custom
storefront control can open the cart with:

```js
section.dispatchEvent(new CustomEvent("cart:open", { bubbles: true }))
```

The trigger does not need to know which profile is assigned. Profile resolution
happens before hydration, so every valid trigger opens the cart already
selected for that page.

The header cart button and badge subscribe to the same cart state as the
drawer. A successful add must therefore be verified in two separate steps:
the Shopify/cart mutation succeeds, and the configured drawer becomes visibly
open with the correct variant, quantity, price, and image.

## Add products from custom sections

Custom swipe decks, quizzes, configurators, comparisons, wishlists,
calculators, guided-selling sections, and other custom interactions can add a
dynamic set of products through the renderer-managed `lx:cart:add-items`
command. Do not hide BuyBoxes or BundleBuilders, call `.click()`, query the
global document, call Shopify directly, or author cart drawer markup.

The initiating element must use `data-lx-control` and a registered lifecycle
handler:

```js
const addButton = lifecycle.query('[data-lx-control="add-selection"]');

addButton.addEventListener("click", () => {
  section.dispatchEvent(
    new CustomEvent("lx:cart:add-items", {
      bubbles: true,
      detail: {
        requestId: crypto.randomUUID(),
        items: selectedProducts.map((product) => ({
          variantId: product.variantId,
          quantity: product.quantity,
          sellingPlanId: product.sellingPlanId ?? null,
          attributes: product.attributes ?? {},
          meta: {
            title: product.title,
            variantTitle: product.variantTitle,
            image: product.image,
            priceAmount: product.priceAmount,
            currencyCode: product.currencyCode,
          },
        })),
        openCart: true,
      },
    }),
  );
});
```

### Payload limits

| Field | Contract |
|---|---|
| `requestId` | Required unique string, maximum 128 characters |
| `items` | 1–50 entries |
| `variantId` | `gid://shopify/ProductVariant/<numeric-id>` |
| `quantity` | Integer from 1–99 after compatible duplicates are combined |
| `sellingPlanId` | Optional `null` or Shopify SellingPlan GID |
| `attributes` | Up to 20 string pairs; keys up to 64 and values up to 255 characters |
| `meta` | Optional selected-product display hints; decimal `priceAmount` and `currencyCode` must be supplied together |
| `openCart` | Optional boolean, defaults to `true` |

Lines combine only when variant, selling plan, and normalized attributes all
match. Different selling plans or attributes remain separate lines.

### Response events

The renderer dispatches bubbling responses from the originating section:

- `lx:cart:add-items:pending`
- `lx:cart:add-items:accepted` — optimistic intent queued; release local controls
- `lx:cart:add-items:confirmed` — Shopify accepted the merchandise
- `lx:cart:add-items:success`
- `lx:cart:add-items:error`

Every response includes `requestId`. Success includes `addedLines`, the
settled current `cart`, and warnings. Error includes a safe `code`, `message`,
`failedItems`, and the current cart when Shopify changed it.

The accepted and confirmed events are available in the optimistic runtime.
Use accepted for responsive controls and confirmed for success copy; keep
success/error listeners for legacy runtimes. Accepted does not confirm a
purchase or price. Display hints must match the selected market and selling
plan. They are never sent as Shopify prices, and missing data uses placeholders.

`requestId` is idempotent for the browser-tab session. An identical retry
replays the prior response without another mutation. Reusing the ID for a
different payload returns `request_id_conflict`.

The renderer performs one `cartCreate` or `cartLinesAdd` request for the
normalized array. If Shopify adds less than requested, Lexsis preserves the
resulting cart, emits `partial_add`, and does not open the drawer or attempt a
second rollback mutation. A fully successful request settles configured
rewards and promotions, then opens the effective Cart V2 drawer when
`openCart` is true.

Common error codes include `invalid_request`, `invalid_request_id`,
`invalid_variant_id`, `invalid_quantity`, `invalid_selling_plan_id`,
`invalid_attributes`, `cart_profile_unavailable`,
`cart_runtime_unavailable`, `unavailable_variant`, `shopify_rejected`,
`partial_add`, `mutation_failed`, and `request_id_conflict`.

Custom cart modules inside a profile can also send eight further shopper
commands; see [Send cart commands](/cart/custom-modules#send-cart-commands).

## Profile configuration

A profile can configure:

- Desktop and mobile cart modes
- Drawer width and motion
- Visual presets, colors, typography, shape, spacing, and responsive overrides
- Line-item layout, media, quantity controls, removal controls, and sale prices
- Module order and enabled modules
- Free-shipping threshold and celebration
- Shopify selling-plan presentation
- Manual and Shopify-powered product offers
- Cumulative reward milestones, automatic gifts, coupons, and payment logos
- Conditional cart rules
- Checkout behavior
- Scoped custom CSS

The required layout modules are cart lines, order summary, and checkout.
Shipping progress, subscription presentation, payment methods, and product
offer modules can be enabled, disabled, and reordered. See
[Cart Styling](/cart/styling) for the design contract.

### Cart module catalog

These are renderer-managed building blocks selected by the effective profile:

| Island | Responsibility |
|---|---|
| [`DrawerShell`](/islands/cart/drawer-shell) | Responsive drawer, sheet, modal, fullscreen, or dropdown container |
| [`CartLines`](/islands/cart/cart-lines) | Product image, variant, quantity, removal, selling plans, and reward-line state |
| [`CartSummary`](/islands/cart/cart-summary) | Subtotal and optional discounts, shipping, and tax rows |
| [`CartCheckoutButton`](/islands/cart/cart-checkout-button) | Checkout CTA, secondary CTA, item count, and payment providers |
| [`CartProgressBar`](/islands/cart/cart-progress-bar) | Simple free-shipping or tier progress |
| [`CartRewardProgress`](/islands/cart/cart-reward-progress) | Cumulative gifts, fixed-price products, discounts, and shipping rewards |
| [`CartCoupons`](/islands/cart/cart-coupons) | Curated offers, applied savings, coupon drawer, and manual code entry |
| [`CartDiscountInput`](/islands/cart/cart-discount-input) | Direct discount-code application |
| [`CartCrossSell`](/islands/cart/cart-cross-sell) | Reactive profile-configured recommendations |

These island references document profile capabilities and styling contracts.
They are not instructions to add cart markup to storefront page source.

### Offers and subscriptions

Offer slots support these placements:

- `header`
- `after_line`
- `after_lines`
- `before_checkout`

An offer can use manually selected Shopify products or Shopify `RELATED` and
`COMPLEMENTARY` recommendations. Product IDs must be Shopify product GIDs.

Subscribe and Save is a line-item capability. It appears only when the product
or variant has real Shopify selling plans. Profiles can control whether the
purchase options are shown and whether they use cards or a select menu, but a
profile cannot create selling-plan eligibility.

## MCP actions

Call `lexsis_discover` with the router and action below for its current schema.
Put the action parameters inside `args` when calling a consolidated tool.
Read full documentation with `lexsis_support` action `get_doc`, for example
`{"path":"cart/profiles.mdx"}` in `args`; responses include complete content
and a SHA-256 revision hash.

| Router | Action | Effect | Approval |
|---|---|---|---|
| `lexsis_cart` | `get` | Read a draft, resolve the effective profile for a page, list profiles, optional audit history | — |
| `lexsis_cart` | `capabilities` | Snapshot schema, module lifecycle, shopper commands, conditions and text paths | — |
| `lexsis_cart` | `promotions` | Promotion readiness and Shopify discounts for a profile or store | — |
| `lexsis_cart` | `preview` | Signed preview URL for a saved draft or sample cart | — |
| `lexsis_cart` | `preview_states` | Six sample-cart URLs with totals, savings, visible modules and static checks | — |
| `lexsis_drafts` | `cart_create` | New unpublished profile from a preset or another profile | — |
| `lexsis_drafts` | `cart_duplicate` | Copy the current draft into a new profile | — |
| `lexsis_drafts` | `cart_edit` | Versioned design, composition and CSS edit; supports `dry_run` | — |
| `lexsis_drafts` | `cart_promotions_edit` | Versioned promotion edit and readiness preview | — |
| `lexsis_drafts` | `cart_set` | Assign a published profile to one page, or clear the page assignment | Live — explicit |
| `lexsis_drafts` | `cart_assign_campaign` | Assign a published profile to a campaign, or clear the override | Live — explicit |
| `lexsis_drafts` | `cart_archive` | Archive an unused profile; `reassign_to` moves its references | Explicit when reassigning |
| `lexsis_live_ops` | `cart_publish` | Publish the approved version and synchronize Shopify discounts | Live — explicit |
| `lexsis_live_ops` | `cart_rollback` | Restore a historical version into a new forward draft | Explicit |
| `lexsis_live_ops` | `cart_set_default` | Change the store default profile | Live — explicit |

`cart_set` and `cart_assign_campaign` live under `lexsis_drafts` for
compatibility, but they **change live resolution**. Only `cart_publish`
writes to Shopify. Rollback is draft-only; separately approve and publish the
restored draft.

`lexsis_cart.capabilities` advertises `lifecycle_access:"mcp_with_approval"`
and `promotion_access:"draft_then_publish"`. It also returns the
`lx:cart:add-items` command contract, the frozen cart snapshot schema, eight
additional shopper commands, condition fields and operators, and declarative
text binding paths. The custom-module script contract is listed separately
under `module_lifecycle`.

## Related

- [Open Cart profiles in the Lexsis app](https://app.trylexsis.com/storefront/settings/cart)
- [Cart Islands](/islands/cart)
- [Quick Add](/islands/commerce/quick-add)
- [Sticky Bar](/islands/commerce/sticky-bar)
