Skip to content
GWP Book a demo

Cart adapters

The widget needs the cart total and currency to apply your minimum-cart rule, to filter offers with their own minimum, and to attach a cart snapshot to the click id at selection time. Pick one of three adapters.

Configuration is stored server-side on your store record (cartAdapter) and shipped in the config response. You do not write it into the page. There is no dashboard field for cartAdapter.config today: tell us the selector and we will set it.

// the shape stored for you; visible in GET /v1/config
"cartAdapter": {
"type": "datalayer" | "window" | "dom",
"config": { /* adapter-specific, see below */ }
}

Values are in major units (59.99, not 5999), matching your displayed prices, and currency is an ISO-4217 code. If we cannot read a currency we fall back to GBP, so always provide one.

Reads window.dataLayer, walking backwards for the most recent entry that carries a cart value, so later pushes always win. It understands two shapes.

GA4 / Universal Analytics shape. For each entry it looks at entry.ecommerce if present, otherwise the entry itself, and reads the first of value, cartValue, cart_total that is present (not null/undefined), then parses it. Strings are parsed, so "59.99" and "£59.99" both work. A present-but-unparseable value is not rescued by cartValue: that entry is skipped entirely and the walk moves on, so do not push a placeholder such as "" or "N/A". Currency comes from currency or currencyCode on the same object, or from any entry's pageAttributes[0].currency / currency / currencyCode.

<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "cart_view",
ecommerce: {
value: 59.99, // required — the cart total, major units
currency: "GBP", // required
items: [ // optional, passed through for reconciliation
{ id: "SKU-1234", qty: 2 },
{ id: "SKU-9876", qty: 1 }
]
}
});
</script>

Push again on every cart change. The loader wraps dataLayer.push at boot, so each push re-runs eligibility, provided window.dataLayer already existed when the tag booted. If your app creates or replaces dataLayer after hydration, the wrap is never installed: dispatch gwp:cartchange after each push instead.

"cartAdapter": { "type": "datalayer" } // no config needed

Flat basket shape (common on enterprise commerce platforms) is also supported without any config: an entry carrying basketTotal (number or string) and optionally basketProducts[], whose line items are read as productId ?? item_id ?? sku ?? id and quantity ?? qty.

window.dataLayer.push({
event: "PageLoad",
basketTotal: "59.99",
basketProducts: [{ item_id: "SKU-1234", quantity: 2 }]
});

Note the asymmetry: the flat basketProducts branch normalises line items to { id, qty }; the GA4 branch passes ecommerce.items through verbatim. The { id, qty } shape in the example above is a recommendation, not a normalising code path.

The cleanest option if you control the front end. Publish a plain object and keep it current.

<script>
window.gwp = window.gwp || {};
window.gwp.cart = {
value: 59.99, // MUST be a real number — a string is rejected
currency: "GBP", // optional, defaults to "GBP"
items: [ // optional
{ id: "SKU-1234", qty: 2 },
{ id: "SKU-9876", qty: 1 }
]
};
</script>

The one hard rule: value must be typeof "number". "59.99" reads as no cart at all. (The datalayer adapter parses strings; this one does not.)

Because assigning to a global is not a DOM mutation, tell the widget when it changes:

function setGwpCart(total, currency, items) {
window.gwp = window.gwp || {};
window.gwp.cart = { value: total, currency, items };
window.dispatchEvent(new Event("gwp:cartchange"));
}
"cartAdapter": { "type": "window" } // no config needed

Last resort, for stores with no data layer and no ability to add a global. We read the text of one element and strip everything that is not a digit or a dot.

"cartAdapter": {
"type": "dom",
"config": {
"totalSelector": "[data-testid='basket-total']", // required
"currency": "GBP" // optional, default "GBP"
}
}
<!-- the element the selector must match -->
<span data-testid="basket-total">£59.99</span>

Constraints:

  • Dot decimal separator only. £1,234.50 reads correctly as 1234.5 (commas are stripped), but 1.234,50 reads as 1.23450. If you display comma-decimal locales, use datalayer or window instead.
  • Negative values are not parsed; a minus sign is stripped.
  • The element must contain the total and nothing that looks like another number.
  • Line items are not read (items is always empty).
  • The read is document-scoped, so on a drawer-first store pick an element that carries the current total wherever the drawer can open.
  • The widget re-reads on DOM changes, so a re-rendered total is picked up automatically.

Needed to associate the shopper's order with their gift. Tried in this order, for every adapter type:

  1. window.gwp.orderId (string).
  2. cartAdapter.config.orderIdSelector, a CSS selector whose element's trimmed textContent is the order id. Works with any adapter type, so a datalayer store can still use a selector here.
  3. window.dataLayer, walked backwards for transactionId, orderId or ecommerce.transaction_id.
  4. ?order= or ?orderId= on the confirmation URL.
<!-- option 1, the most reliable -->
<script>
window.gwp = window.gwp || {};
window.gwp.orderId = "1000123456";
</script>
// option 2
"cartAdapter": {
"type": "datalayer",
"config": { "orderIdSelector": "[data-testid='order-number']" }
}
  • The claim moment — what happens after the order is placed, on your confirmation page or in your own email.
  • Verify and troubleshoot — prove the adapter is actually reading, because a broken read does not look broken.