Cart tracking

Cart tracking

Tocadule keeps a live snapshot of every visitor's cart called cart_state. It is the single source of truth for cart cards in popups and emails, abandoned-cart automations, the "has items in cart" segment, and product-in-cart targeting. You feed it with one call:

tocadule.cart({ items, total, currency, item_count })

Send the whole current cart on every change (add, remove, quantity edit) — not a delta. Each call replaces the previous snapshot.

On the platforms we auto-detect (Shopify, IdeaSoft, WooCommerce, T-Soft, Ticimax, ikas) the snippet reads and re-reads the cart for you — you don't have to call tocadule.cart() at all. There's also a platform-agnostic passive reader that recognises cart data by shape in your store's network responses, so most stores get cart_state with zero code. Call tocadule.cart() yourself only when auto-detection doesn't cover your store, or to be explicit on a headless/custom build.

Install

tocadule.cart() is part of the standard snippet. The tiny inline stub queues any calls into tocadule._q and the async snippet.js replays them once it loads, so you can call tocadule.cart() immediately — before the main script has finished downloading.

<script>window.tocadule=window.tocadule||{};window.tocadule._q=window.tocadule._q||[];['track','identify','cart','requestPush'].forEach(function(m){window.tocadule[m]=window.tocadule[m]||function(){window.tocadule._q.push([m,arguments])}});</script>
<script async data-workspace="YOUR_WORKSPACE_ID" src="https://app.tocadule.com/snippet.js"></script>

Sending the cart

Loop over every line in the cart and build one object per line. This is the exact script from the in-app Events guide:

<script>
  // Send your WHOLE current cart on EVERY change (add / remove / quantity).
  // Build items[] by looping over every line in the cart — one object per line.
  // The values below are EXAMPLES — replace them with your real cart data.
  //   REQUIRED per item: product_id, name, price (number), quantity (number)
  //   OPTIONAL per item: image, url  (make the cart cards look nice)
  if (window.tocadule) {
    tocadule.cart({
      items: [
        { product_id: '101', name: 'Blue T-Shirt', price: 199.90, quantity: 2, image: 'https://yourshop.com/img/blue.jpg', url: 'https://yourshop.com/blue-tshirt' },
        { product_id: '250', name: 'Black Cap',    price: 149.00, quantity: 1, image: 'https://yourshop.com/img/cap.jpg',  url: 'https://yourshop.com/black-cap' }
        // …repeat one object for EACH product currently in the cart
      ],
      total: 549.80,     // cart subtotal as a number
      currency: 'TRY',
      item_count: 3       // total quantity across all lines
      // No email / PII here on purpose — the cart stays lean. Once any other event
      // carries the shopper's email (purchase, or a logged-in product_view /
      // checkout), we link the contact and the cart resolves off the anonymous_id.
    });
  }
</script>

Top-level fields

FieldTypeRequiredNotes
itemsarrayyesOne object per cart line. See below.
totalnumberrecommendedCart subtotal as a number. Coerced with parseFloat; defaults to 0.
currencystringrecommendedISO code, e.g. TRY, USD. Used for cart cards and purchase gap-fill.
item_countnumberrecommendedTotal quantity across all lines. Defaults to items.length if omitted.

Item fields

product_id, name, price, and quantity are required — they're load-bearing for cart cards and for forwarding to ad destinations. The rest are enrichment that makes the cards look better and improves matching.

FieldRequiredNotes
product_idyesJoin key across events, and the content_id sent to ad catalogs. Coerced to a string.
nameyesProduct name shown on cards.
priceyesUnit price as a number.
quantityyesLine quantity as a number. Defaults to 1.
imagenoProduct image URL — used on popup/email cart cards.
urlnoProduct page URL.
variantnoVariant label (size / colour).
brandnoBrand / vendor.
categorynoProduct category.
skunoStock code.

Any extra scalar fields you put on an item (e.g. gtin, model_code) are carried through untouched — canonical keys always win, extras fill the gaps. (Keys prefixed with _, function values, and empty/null values are the only things stripped.)

The Cart health panel in your workspace samples recent cart_state items and shows the present-rate per field, so you can confirm the required fields are actually arriving and spot a product_id or price that's coming through empty.

cart_state is PII-free by design

Never put email, phone, or a name on tocadule.cart(). The cart snapshot carries only cart contents plus the visitor's anonymous_id. It resolves to a contact through the identity map — so as soon as any event carries an identifier (a purchase, or a logged-in product_view / checkout_started), the contact is linked and the cart attaches to them via the anonymous_id.

That means you need at least one linking signal per visitor somewhere in their journey — either a one-time tocadule.identify(email, …) at login/sign-up, or an identifier (email / phone) on any ordinary event — but it does not have to be on the cart. Keeping the cart lean avoids leaking PII into a high-frequency, snapshot-style event.

⚠️

The automatic/passive cart reader only ever reads cart contents from your store — it never reads or attaches customer PII. Identity always comes from a separate identified event.

How cart_state powers other things

  • add_to_cart is derived from cart_state. On non-Shopify stores — when add_to_cart is left to auto-detection (not sent manually or via dataLayer) — the snippet diffs consecutive cart snapshots and fires add_to_cart for any line whose quantity increased. The first snapshot never fires (no previous cart to diff), and if cart_state stops flowing, add_to_cart stops too.
  • Abandoned-cart automations read the visitor's latest non-empty cart_state ("has items + idle").
  • Popup and email cart cards render from the latest cart_state ({cart_total}, {cart_count}, {cart_currency}, and the dynamic product-upsell block).
  • Purchase gap-fill: when a purchase arrives without line items (and/or without a currency), we stitch them from the visitor's most recent non-empty cart (checkout_started preferred, else cart_state) within the last 7 days, matched by anonymous_id or contact_id. Items/currency already on the purchase are left untouched.

Deduplication

You never set dedup IDs. Browser events dedup on anonymous_id; external/server events dedup on source_event_id. On top of that, tocadule.cart() throttles itself: if the cart contents and count are unchanged and the last snapshot fired within the last 60 seconds, the call is skipped. This roughly halves cart_state volume on SPA-style stores that re-fire on every navigation, without starving abandonment automations. If both a platform hook and the passive reader see the same cart, the passive call defers to the platform hook.

Consent

cart_state respects the same two consent lanes as every other event:

  • Analytics lane (consent_state) gates storing the anonymous_id. In cookieless mode the cart still sends, but without a persistent visitor ID.
  • Advertising lane (ad_consent) gates forwarding cart-derived events to ad destinations (Meta / TikTok / Google).

Email/SMS opt-in is a separate merchant preference and does not affect cart tracking.