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
| Field | Type | Required | Notes |
|---|---|---|---|
items | array | yes | One object per cart line. See below. |
total | number | recommended | Cart subtotal as a number. Coerced with parseFloat; defaults to 0. |
currency | string | recommended | ISO code, e.g. TRY, USD. Used for cart cards and purchase gap-fill. |
item_count | number | recommended | Total 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.
| Field | Required | Notes |
|---|---|---|
product_id | yes | Join key across events, and the content_id sent to ad catalogs. Coerced to a string. |
name | yes | Product name shown on cards. |
price | yes | Unit price as a number. |
quantity | yes | Line quantity as a number. Defaults to 1. |
image | no | Product image URL — used on popup/email cart cards. |
url | no | Product page URL. |
variant | no | Variant label (size / colour). |
brand | no | Brand / vendor. |
category | no | Product category. |
sku | no | Stock 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_cartis derived fromcart_state. On non-Shopify stores — whenadd_to_cartis left to auto-detection (not sent manually or via dataLayer) — the snippet diffs consecutive cart snapshots and firesadd_to_cartfor any line whose quantity increased. The first snapshot never fires (no previous cart to diff), and ifcart_statestops flowing,add_to_cartstops 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
purchasearrives without line items (and/or without acurrency), we stitch them from the visitor's most recent non-empty cart (checkout_startedpreferred, elsecart_state) within the last 7 days, matched byanonymous_idorcontact_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 theanonymous_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.