GA4 Tracking · 2 events to fix

view_item & add_to_cart

The situation on the live store, then exactly what to send and when. With the code to drop in.

⬇ Download the Excel (codes & variables)
view_itemThe situation

The variant is unknown when the page loads

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.

1 · No variant Landing on the t-shirt: gender not even chosen. No variant, no size.
2 · Configuring Gender, product, colour, collar. The variant gets built step by step.
3 · Price moves Selecting "Pack 2 produits": 129€ becomes 206,40€. Still nothing in the cart.
No variant

at page load. Only a stable group ID is known.

No fixed price

the displayed price changes with the pack toggle.

Group ID

never changes, whatever the size, colour or pack.

add_to_cartThe situation

Adding one item can change the price of another

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.

1 · One add, many lines Mattress + pillow (-50%) + cover bundled into a single "add to cart".
2 · Conditional offer Adding the sommier triggers a pack price (+899,40€ instead of 1499€).
3 · Re-priced In the cart BOTH lines drop (-449,70 / -209,70). Total 1 538,60€.
Was broken

AddingWell listened on the click and swallowed the event.

1 add = N lines

a pack adds several products at once.

Retroactive

a new add can lower the price of an item already in the cart.

view_itemThe solution

What to send, and when

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.

1When to fire it
Once, when the product page loads. Do not re-fire view_item every time the customer changes a size or option, that would inflate product views.
2What changes (vs the current dataLayer)
VariableValueSend?
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
price89keep · display price, not final value
configuration_state"unresolved"add · new flag
3When the customer picks options
Send a separate product_variant_selected event with the resolved variant. view_item stays untouched.
on page load
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
    }]
  }
});
when a variant is selected
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
    }]
  }
});
Result: a clean "product viewed" metric. The real value is captured later, at add_to_cart, where the variant and pack are locked.
add_to_cartThe solution

What to send, and when

Fire add_to_cart for the real action, then a cart_updated with the full recalculated cart so the total is always correct.

1When to fire add_to_cart
After the cart confirms the add, not on the button click (that is what AddingWell broke). One event, with all the lines actually added.
2What to send (same fields as the dataLayer)
VariableValueSend?
value89yes · sum of added lines
item_id"{{item_group_id}}"yes · GROUP ID
item_variant_id"51433313272154"yes · now resolved
sku"FR-SOUTIEN-GORGE-..."yes
price89yes · final line price
discount0yes
quantity1yes
3Then, after every cart change
Send cart_updated with the full recalculated cart. This handles the case where a new add lowers another line's price. Never fake a remove_from_cart to patch it.
when the add is confirmed
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"
    }]
  }
});
after every cart change
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
      }
    ]
  }
});
Without 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€).