The situation on the live store, then exactly what to send and when. With the code to drop in.
⬇ Download the Excel (codes & variables)On the product page there is no variant yet. The customer first picks gender, product, colour, collar and size. Only a stable group ID exists up front, and the price can change before anything is added.
Landing on the t-shirt: gender not even chosen. No variant, no size.
Gender, product, colour, collar. The variant gets built step by step.
Selecting "Pack 2 produits": 129€ becomes 206,40€. Still nothing in the cart.at page load. Only a stable group ID is known.
the displayed price changes with the pack toggle.
never changes, whatever the size, colour or pack.
One "add to cart" can add several lines, and a later add can retroactively re-price an item already in the cart (packs, bundle offers). So a value fixed at add time no longer matches the real cart.
Mattress + pillow (-50%) + cover bundled into a single "add to cart".
Adding the sommier triggers a pack price (+899,40€ instead of 1499€).
In the cart BOTH lines drop (-449,70 / -209,70). Total 1 538,60€.AddingWell listened on the click and swallowed the event.
a pack adds several products at once.
a new add can lower the price of an item already in the cart.
We keep the existing dataLayer structure and every field. Event names take a _custom suffix (the standard GA4 ecommerce names are already in use, so we can't reuse them). Only two value rules change: leave the variant fields empty until a variant is chosen, and treat the price as a display value, not the final conversion value.
| Variable | Value | Send? |
|---|---|---|
| item_id | "{{item_group_id}}" | change · GROUP ID (regroups variants) |
| item_product_id | "10243669852506" | keep · Shopify product id |
| item_variant_id | "" | empty · until variant chosen |
| item_variant | "" | empty · until variant chosen |
| sku | "" | empty · until variant chosen |
| price | 89 | keep · display price, not final value |
| configuration_state | "unresolved" | add · new flag |
dataLayer.push({ event: "view_item_custom", // _custom (standard in use) ecommerce: { currency: "EUR", value: 89, // display price, not final items: [{ item_id: "{{item_group_id}}", // GROUP id (not product id) item_product_id: "10243669852506", // Shopify product id item_product_title: "Soutien-gorge", item_name: "Soutien-gorge", item_brand: "Percko", item_category: "", item_variant: "", // empty item_variant_id: "", // empty item_variant_title: "", // empty sku: "", // empty price: 89, // display price discount: 0, inventory_quantity: 8569, quantity: 1, item_list_id: null, item_list_name: null, url: "/products/soutien-gorge", configuration_state: "unresolved" // NEW }] } });
dataLayer.push({ event: "product_variant_selected", // NEW custom event ecommerce: { currency: "EUR", value: 89, items: [{ item_id: "{{item_group_id}}", // GROUP id, unchanged item_variant: "Noir / S A-B", // now filled item_variant_id: "51433313272154", // now filled item_variant_title: "Noir / S A-B", sku: "FR-SOUTIEN-GORGE-NOIR-S-A-B", price: 89, configuration_state: "resolved", pack_selected: false }] } });
add_to_cart, where the variant and pack are locked.Fire add_to_cart for the real action, then a cart_updated with the full recalculated cart so the total is always correct.
| Variable | Value | Send? |
|---|---|---|
| value | 89 | yes · sum of added lines |
| item_id | "{{item_group_id}}" | yes · GROUP ID |
| item_variant_id | "51433313272154" | yes · now resolved |
| sku | "FR-SOUTIEN-GORGE-..." | yes |
| price | 89 | yes · final line price |
| discount | 0 | yes |
| quantity | 1 | yes |
dataLayer.push({ event: "add_to_cart_custom", // _custom (standard in use) ecommerce: { currency: "EUR", value: 89, // sum of added lines items: [{ item_id: "{{item_group_id}}", // GROUP id item_name: "Soutien-gorge", item_variant: "Noir / S A-B", item_variant_id: "51433313272154", sku: "FR-SOUTIEN-GORGE-NOIR-S-A-B", price: 89, // final line price discount: 0, quantity: 1, url: "/products/soutien-gorge" }] } });
dataLayer.push({ event: "cart_updated", // NEW custom event change_type: "discount_applied", ecommerce: { currency: "EUR", value: 1538.60, // recalculated total = truth items: [ { item_id: "{{item_group_id}}", item_name: "Sommier zoné", item_variant: "80x200", item_variant_id: "...", sku: "...", price: 1049.30, discount: 449.70, quantity: 1 }, { item_id: "{{item_group_id}}", item_name: "Matelas hybride", item_variant: "80x200", item_variant_id: "...", sku: "...", price: 489.30, discount: 209.70, quantity: 1 } ] } });
cart_updated: add_to_cart says 1 049€ for the sommier, the mattress silently drops to 489€, and the funnel value no longer matches the order. cart_updated always carries the real total (here 1 538,60€).