Cart & Upsell
Cart & Upsell

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

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)

MethodReturnsDescription
NocuCart.addToCart(data)PromiseAdd an item and open the drawer
NocuCart.openDrawer()voidOpen the cart drawer
NocuCart.closeDrawer()voidClose the cart drawer
NocuCart.isCartOpen()booleanWhether the drawer is currently open
NocuCart.refreshAndOpen()PromisePoll until cart item count increases, then open
NocuCart.refresh(open?)PromiseFetch cart once; open drawer if open is true (default: true)
NocuCart.triggerAddToCart(variantId, quantity?, properties?)voidDispatch 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.

EventWhenevent.detail
nocu:cart:openedDrawer opens
nocu:cart:openOpen state change{ source, timestamp }
nocu:cart:closeClose state change{ source, timestamp }
nocu:cart:closedDrawer fully closed
nocu:cart:addedItem added / cart updated{ item?, cart }
nocu:cart:errorCart operation failed{ error }
nocu:cart:fetch-errorCart 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:

EventWhenevent.detail
nocu:widget-viewWidget is shown{ widgetName }upsells, volume_bundle, or tiered_bar
nocu:widget-accepted:upsellUpsell accepted{ product_id, variant_id, product_title, variant_title }
nocu:widget-accepted:volume_bundleVolume bundle acceptedproduct / variant fields + bundle qty
nocu:widget-accepted:free-giftFree gift added{ variant_id }
nocu:widget-removed:volume_bundleVolume bundle removedproduct / variant fields + bundle qty
nocu:widget-tiered-bar:completedTier unlocked{ tier_type }
nocu:widget-tiered-bar:removedTier lost{ tier_type }
nocu:tiered-bar:updatedProgress 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.

EventDirectionPurpose
showNocuCheckoutPopupTheme → AppShow checkout upsell popup
hideNocuCheckoutPopupBothHide checkout popup / continue checkout
showAddToCartPopupTheme → AppShow add-to-cart upsell popup
popupReadyApp → ThemePopup finished loading
ocu_cart_changedCart watcher → AppsCart changed; re-evaluate funnels
ncu_cart_items_updatedCart watcher → AppsEnriched ncuCartItems refreshed
timerEndedTimer → ListenersOffer 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.

GlobalTypeDescription
window.NocuCartobjectCart public API — see above
window.nocuCartMetaarrayActive cart drawer configurations
window.ncuCartItemsarrayCart items enriched with tags, collections, compare-at price
window.nocuCurrentPageProductobjectCurrent product JSON (product pages)
window.nocuCurrentCollectionsarrayCollections for the current product
window.nocuCurrentTagsstring[]Tags for the current product
window.nocuCustomerDataobject | undefined{ orderCount, tags } when a customer is logged in
window.nocuCountrystringCustomer's country ISO code
window.rootRoutestringLocale-aware root path (/ or /en/)
window.nocuShopCurrencystring | undefinedShopify money format string (when the currency-format setting is enabled)
window.nvdOcuShopCurrencystringShop 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 action containing /cart/add, or with class product-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.

SettingTypeDefaultPurpose
Below Add to Cart Widget Selectortextform[action='/cart/add'].formCSS 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 Selectortext.cart__ctas, .nocu-cart-footer__checkout-btnCSS 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 Selectortext[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 Selectortext[name='checkout'],[name='checkout'] *CSS selector used to intercept Checkout clicks and show the checkout upsell popup before redirecting.
Use Shopify's native price formattingcheckboxfalseWhen 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 Stylestextarea(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.

MethodEndpointPurpose
GET/cart.jsFetch cart
POST/cart/add.jsAdd items
POST/cart/update.jsUpdate quantities / attributes
POST/cart/change.jsChange 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'