AgentReady
By Dylan HuntMay 13th, 2026ShopifyPixelsCustomer events

Tracking Add-to-Cart and Checkout Events on Shopify with analytics.subscribe

Tracking Add-to-Cart and Checkout Events on Shopify with analytics.subscribe

Every tracking migration ends at the same question: what exactly do Shopify's customer events give me, and what do I forward to my analytics? This is the reference I wish existed when I started. The funnel events that matter, their real payload shapes, and the GA4 wiring for each.

If you haven't created a pixel yet, start with adding a custom pixel without an app, then come back. Shopify's own standard events reference is the canonical field list; what follows is the practical subset.

The funnel in four events

analytics.subscribe("product_viewed", (event) => { /* PDP view */ });
analytics.subscribe("product_added_to_cart", (event) => { /* ATC */ });
analytics.subscribe("checkout_started", (event) => { /* begin checkout */ });
analytics.subscribe("checkout_completed", (event) => { /* purchase */ });

Every handler receives the same envelope: event.context is a snapshot of where it happened (page, navigator, window) and event.data is the commerce payload, different per event. You also get event.timestamp and event.id for deduplication.

product_added_to_cart

The payload is a cartLine:

analytics.subscribe("product_added_to_cart", (event) => {
  const line = event.data.cartLine;

  line.quantity;                          // how many were just added
  line.cost.totalAmount.amount;           // line cost, e.g. "39.98"
  line.cost.totalAmount.currencyCode;     // "USD"
  line.merchandise.product.title;         // parent product
  line.merchandise.title;                 // variant title
  line.merchandise.sku;                   // variant SKU
  line.merchandise.price.amount;          // unit price
});

And the GA4 mapping:

gtag("event", "add_to_cart", {
  currency: line.cost.totalAmount.currencyCode,
  value: Number(line.cost.totalAmount.amount),
  items: [{
    item_id: line.merchandise.sku || line.merchandise.product.id,
    item_name: line.merchandise.product.title,
    item_variant: line.merchandise.title,
    quantity: line.quantity,
    price: Number(line.merchandise.price.amount),
  }],
});

checkout_started and checkout_completed

Both carry event.data.checkout. Same shape, two moments in time:

analytics.subscribe("checkout_completed", (event) => {
  const checkout = event.data.checkout;

  checkout.order?.id;                  // the order, only on completed
  checkout.totalPrice.amount;          // grand total after discounts + tax
  checkout.subtotalPrice.amount;       // before shipping and tax
  checkout.currencyCode;
  checkout.discountApplications;       // applied discounts, with codes
  checkout.lineItems;                  // [{ title, quantity, variant, ... }]
  checkout.email;                      // when provided and consented
});

The purchase event, mapped:

analytics.subscribe("checkout_completed", (event) => {
  const checkout = event.data.checkout;
  gtag("event", "purchase", {
    transaction_id: checkout.order?.id,
    currency: checkout.currencyCode,
    value: Number(checkout.totalPrice.amount),
    coupon: checkout.discountApplications?.[0]?.title,
    items: checkout.lineItems.map((item) => ({
      item_id: item.variant?.sku || item.variant?.product?.id,
      item_name: item.title,
      quantity: item.quantity,
      price: Number(item.variant?.price?.amount ?? 0),
    })),
  });
});

Two things worth internalizing before you ship this:

  • checkout_completed fires when the thank-you page renders. A buyer who closes the tab a beat early is a real order your pixel never saw. Browser-side numbers run a little under Shopify's order count for everyone. That's the nature of pixels, not a bug in yours. Treat the Shopify order admin as the source of truth and your analytics as directional.
  • Consent gates delivery. If the visitor hasn't granted the consent categories your pixel declared, the events simply don't arrive. Don't burn an afternoon debugging what is actually your privacy settings working as intended.

Custom events when the standard ones aren't enough

The sandbox can't watch your DOM. For anything Shopify doesn't publish, like size-guide opens or quiz completions, publish from theme code where the interaction happens:

// In the theme (has DOM access):
document.querySelector("#size-guide")?.addEventListener("click", () => {
  Shopify.analytics.publish("size_guide_opened", {
    productHandle: window.location.pathname.split("/").pop(),
  });
});
// In the pixel:
analytics.subscribe("size_guide_opened", (event) => {
  gtag("event", "size_guide_opened", {
    product_handle: event.customData?.productHandle,
  });
});

That split, theme publishes and pixel subscribes, keeps every outbound network call in one auditable place. The publish API is documented in the Web Pixels API reference.

Verify against reality

After wiring, place a test order and check that three numbers agree: Shopify's order total, the purchase event's value in your tracker's debug view, and the line-item count. If they match on one real order, your mapping is right. If conversions later drift low by a few percent, re-read the first note above before rewriting anything.

The same structured thinking applies to how AI agents read your store. Events are how you measure; structured data is how you're understood. The Customer Events migration guide covers the first, and AgentReady covers the second.

Quick answers

Frequently asked questions

Which Shopify customer event fires on purchase?
checkout_completed. It fires once when the thank-you page renders, and its payload carries the order id, total price, currency, discounts, and full line items. Everything a purchase event needs.
Why is my add_to_cart event not firing?
The two usual causes: the pixel is saved but not Connected in Settings > Customer events, or the visitor hasn't given the consent your pixel's privacy settings require. Test in a region without consent requirements first, then with consent granted.
Can I track custom events that Shopify doesn't publish?
Yes. From theme or app code with DOM access, call Shopify.analytics.publish with your event name and payload, then subscribe to that name in your pixel. The pixel sandbox can't watch the DOM, so publishing is how you bridge into it.

See where your store stands

Get found and recommended by AI shopping assistants.

Run the free AI-Readiness Checker to see, in about ten seconds, how ChatGPT, Perplexity, and Google read your store today and exactly what is holding it back. Then AgentReady fixes the gaps for you, adding Schema.org structured data, an llms.txt directory, and an ongoing audit. Install free; every software feature is $29/mo.

Comments

Every comment here comes from a verified email. Write yours, confirm from your inbox, and it's live.

Loading comments…

Leave a comment

ShareXLinkedInFacebook

Written by Dylan Hunt, Founder, AgentReady. AgentReady measures what AI says about Shopify stores and helps teams improve the answer. See plans.