Cart & Upsell
Public Storefront API
Control the cart drawer, listen for cart and upsell events, and integrate NCU with your Shopify theme.
Use this API to control the NCU cart drawer, listen for cart and upsell events, and integrate with your Shopify theme.
Requirements
The AI Cart & Upsell app embed must be enabled on the live theme. window.NocuCart is available after the cart script mounts — wait for DOMContentLoaded (or check for its existence) before calling it.
Everything on this page is part of the storefront-facing script that already ships to every visitor's browser, so it's safe to build against. Methods, events, and settings listed here are the supported integration surface — internal debugging helpers aren't documented here since they're not meant for theme code to depend on.
Cart API (window.NocuCart)
| Method | Returns | Description |
|---|---|---|
NocuCart.addToCart(data) | Promise | Add an item and open the drawer |
NocuCart.openDrawer() | void | Open the cart drawer |
NocuCart.closeDrawer() | void | Close the cart drawer |
NocuCart.isCartOpen() | boolean | Whether the drawer is currently open |
NocuCart.refreshAndOpen() | Promise | Poll until cart item count increases, then open |
NocuCart.refresh(open?) | Promise | Fetch cart once; open drawer if open is true (default: true) |
NocuCart.triggerAddToCart(variantId, quantity?, properties?) | void | Dispatch a nocu:cart:add event |
addToCart accepts the same payload shape as Shopify's /cart/add.js:
type AddToCartData = {
id: number | string // Variant ID
quantity: number
properties?: Record<string, any>
selling_plan?: number
}
// Add item and open drawer
await window.NocuCart.addToCart({
id: 123456789,
quantity: 2,
properties: {
'Gift Message': 'Happy Birthday!',
},
})
// Open / close
window.NocuCart.openDrawer()
window.NocuCart.closeDrawer()
// Check open state
if (window.NocuCart.isCartOpen()) {
console.log('Drawer is open')
}
// After your theme adds an item via AJAX
await window.NocuCart.refreshAndOpen()
// Refresh cart data without opening
await window.NocuCart.refresh(false)
// Trigger add via helper (fires custom event)
window.NocuCart.triggerAddToCart(123456789, 1, { Color: 'Blue' })
You can also add to cart by dispatching a custom event directly, without touching window.NocuCart:
document.dispatchEvent(
new CustomEvent('nocu:cart:add', {
detail: {
variantId: 123456789,
quantity: 2,
properties: { Color: 'Blue' },
},
})
)
If your theme already calls Shopify's AJAX cart directly, refresh the drawer afterward so it picks up the change:
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: variantId, quantity: 1 }),
})
.then((res) => res.json())
.then(() => window.NocuCart.refreshAndOpen())
Cart events
Listen on document unless noted otherwise.
| Event | When | event.detail |
|---|---|---|
nocu:cart:opened | Drawer opens | — |
nocu:cart:open | Open state change | { source, timestamp } |
nocu:cart:close | Close state change | { source, timestamp } |
nocu:cart:closed | Drawer fully closed | — |
nocu:cart:added | Item added / cart updated | { item?, cart } |
nocu:cart:error | Cart operation failed | { error } |
nocu:cart:fetch-error | Cart fetch failed | { error, retryCount } |
document.addEventListener('nocu:cart:added', (event) => {
console.log('Cart updated:', event.detail.cart)
console.log('Added item:', event.detail.item)
})
document.addEventListener('nocu:cart:error', (event) => {
console.error('Cart error:', event.detail.error)
})
document.addEventListener('nocu:cart:opened', () => {
console.log('Drawer opened')
})
Widget analytics events fire alongside the lifecycle events above, useful for wiring NCU activity into your own analytics:
| Event | When | event.detail |
|---|---|---|
nocu:widget-view | Widget is shown | { widgetName } — upsells, volume_bundle, or tiered_bar |
nocu:widget-accepted:upsell | Upsell accepted | { product_id, variant_id, product_title, variant_title } |
nocu:widget-accepted:volume_bundle | Volume bundle accepted | product / variant fields + bundle qty |
nocu:widget-accepted:free-gift | Free gift added | { variant_id } |
nocu:widget-removed:volume_bundle | Volume bundle removed | product / variant fields + bundle qty |
nocu:widget-tiered-bar:completed | Tier unlocked | { tier_type } |
nocu:widget-tiered-bar:removed | Tier lost | { tier_type } |
nocu:tiered-bar:updated | Progress bar updated | { orderValue, headerText, progressPercentage } |
document.addEventListener('nocu:widget-view', (event) => {
console.log('Widget viewed:', event.detail.widgetName)
})
document.addEventListener('nocu:widget-accepted:upsell', (event) => {
console.log('Upsell accepted:', event.detail)
})
document.addEventListener('nocu:widget-tiered-bar:completed', (event) => {
console.log('Tier completed:', event.detail.tier_type)
})
Pre-purchase / upsell events
These events are dispatched and listened for on window. They bridge your theme and the pre-purchase (checkout-adjacent) upsell app.
| Event | Direction | Purpose |
|---|---|---|
showNocuCheckoutPopup | Theme → App | Show checkout upsell popup |
hideNocuCheckoutPopup | Both | Hide checkout popup / continue checkout |
showAddToCartPopup | Theme → App | Show add-to-cart upsell popup |
popupReady | App → Theme | Popup finished loading |
ocu_cart_changed | Cart watcher → Apps | Cart changed; re-evaluate funnels |
ncu_cart_items_updated | Cart watcher → Apps | Enriched ncuCartItems refreshed |
timerEnded | Timer → Listeners | Offer countdown finished |
// Show / hide checkout upsell popup
window.dispatchEvent(new CustomEvent('showNocuCheckoutPopup'))
window.dispatchEvent(new CustomEvent('hideNocuCheckoutPopup'))
// Show add-to-cart upsell popup
window.dispatchEvent(new CustomEvent('showAddToCartPopup'))
// React when cart changes
window.addEventListener('ocu_cart_changed', (event) => {
console.log('Cart changed:', event.detail)
})
window.addEventListener('ncu_cart_items_updated', () => {
console.log('Enriched cart items:', window.ncuCartItems)
})
window.addEventListener('popupReady', () => {
console.log('Upsell popup is ready')
})
Window globals
Injected by the theme app embed once it mounts.
| Global | Type | Description |
|---|---|---|
window.NocuCart | object | Cart public API — see above |
window.nocuCartMeta | array | Active cart drawer configurations |
window.ncuCartItems | array | Cart items enriched with tags, collections, compare-at price |
window.nocuCurrentPageProduct | object | Current product JSON (product pages) |
window.nocuCurrentCollections | array | Collections for the current product |
window.nocuCurrentTags | string[] | Tags for the current product |
window.nocuCustomerData | object | undefined | { orderCount, tags } when a customer is logged in |
window.nocuCountry | string | Customer's country ISO code |
window.rootRoute | string | Locale-aware root path (/ or /en/) |
window.nocuShopCurrency | string | undefined | Shopify money format string (when the currency-format setting is enabled) |
window.nvdOcuShopCurrency | string | Shop currency code (e.g. USD) |
ncuCartItems item shape:
{
handle: 'product-handle',
variant_id: 123456789,
product_id: 987654321,
compare_at_price: '29.99',
tags: ['sale', 'bundle'],
collections: [
{ id: 111, title: 'Best Sellers' },
],
}
nocuCustomerData shape:
{
orderCount: 3,
tags: ['vip', 'wholesale'],
}
Automatic behavior
No custom code is required for common flows — the embed handles these automatically:
- Hijacks forms with an
actioncontaining/cart/add, or with classproduct-form - Opens the drawer after Add to Cart clicks (
[name="add"],.add-to-cart,[data-add-to-cart],.product-form__submit, etc.) - Hijacks header cart icon clicks (see selectors below)
- Closes conflicting theme cart drawers and uses a high z-index so the NCU drawer stays on top
Supported cart icon selectors:
a[href="/cart"]
.cart-icon
.header-cart
.cart-link
.cart-drawer-toggle
.js-cart-link
[data-cart-icon]
[data-cart-toggle]
button[aria-label="Open cart drawer"]
.site-header__cart
.header__icon--cart
.cart-count-bubble
#cart-icon-bubble
.header-actions__cart-icon
button.button.header-actions__action[aria-label^="Open cart"]
Icons added to the page later (e.g. by a mega-menu or drawer that renders after load) are picked up automatically via a MutationObserver.
App embed settings
Configured in Online Store → Themes → Customize → App embeds → AI Cart & Upsell. These settings control where upsell widgets inject, which buttons get intercepted, price formatting, and custom CSS.
| Setting | Type | Default | Purpose |
|---|---|---|---|
| Below Add to Cart Widget Selector | text | form[action='/cart/add'].form | CSS selector for the element after which the Below Add to Cart upsell widget is injected on product pages. Update this if your theme uses a non-standard product form. |
| Above Checkout Widget Selector | text | .cart__ctas, .nocu-cart-footer__checkout-btn | CSS selector for the element before which the Above Checkout upsell widget is injected (cart page or NCU cart footer). Supports comma-separated selectors. |
| Add to Cart Button Selector | text | [name='add'],[name='add'] * | CSS selector used to detect Add to Cart clicks and show the add-to-cart upsell popup. Include child selectors (*) when the click target is an icon/span inside the button. |
| Checkout Button Selector | text | [name='checkout'],[name='checkout'] * | CSS selector used to intercept Checkout clicks and show the checkout upsell popup before redirecting. |
| Use Shopify's native price formatting | checkbox | false | When enabled, sets window.nocuShopCurrency to the shop's money_format so cart prices follow Shopify/theme/currency-app formatting. When disabled, NCU formats prices itself for consistent display across themes. |
| Custom Styles | textarea | (empty) | Extra CSS injected for the pre-purchase upsell UI. Use to override spacing, colors, or layout without editing theme files. |
When to change selectors — update them if:
- Upsell widgets don't appear next to Add to Cart or Checkout
- The add-to-cart or checkout popup never opens on click
- Your theme uses custom button markup (no
name="add"/name="checkout")
Example for a custom product form:
Below Add to Cart Widget Selector:
.product-form__buttons
Add to Cart Button Selector:
.product-form__submit, .product-form__submit *
Shopify cart endpoints
NCU reads and writes the cart through Shopify's own AJAX Cart API — it doesn't maintain a separate cart of its own. Requests append ?ncucart=1 so they're identifiable in your server logs, and all requests respect window.rootRoute for multi-language storefronts.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /cart.js | Fetch cart |
POST | /cart/add.js | Add items |
POST | /cart/update.js | Update quantities / attributes |
POST | /cart/change.js | Change a line item |
Quick reference
// Control
NocuCart.addToCart({ id, quantity, properties? })
NocuCart.openDrawer()
NocuCart.closeDrawer()
NocuCart.isCartOpen()
NocuCart.refreshAndOpen()
NocuCart.refresh(open?)
NocuCart.triggerAddToCart(variantId, quantity?, properties?)
// Listen (document)
'nocu:cart:opened' | 'nocu:cart:added' | 'nocu:cart:error'
'nocu:widget-view' | 'nocu:widget-accepted:upsell' // ...
// Listen / dispatch (window)
'showNocuCheckoutPopup' | 'hideNocuCheckoutPopup' | 'showAddToCartPopup'
'ocu_cart_changed' | 'ncu_cart_items_updated' | 'popupReady'