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.
datalayer: the default
Section titled “datalayer: the default”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 neededFlat 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.
window: you publish a global
Section titled “window: you publish a global”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 neededdom: read the rendered total
Section titled “dom: read the rendered total”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.50reads correctly as1234.5(commas are stripped), but1.234,50reads as1.23450. If you display comma-decimal locales, usedatalayerorwindowinstead. - 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 (
itemsis 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.
Reading the order id (confirmation page)
Section titled “Reading the order id (confirmation page)”Needed to associate the shopper's order with their gift. Tried in this order, for every adapter type:
window.gwp.orderId(string).cartAdapter.config.orderIdSelector, a CSS selector whose element's trimmedtextContentis the order id. Works with any adapter type, so adatalayerstore can still use a selector here.window.dataLayer, walked backwards fortransactionId,orderIdorecommerce.transaction_id.?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']" }}Where next
Section titled “Where next”- 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.