Ecommerce Tracking
GA4 ecommerce tracking is not difficult — but it is unforgiving. Get the data structure right and the Shopping Behavior report fills itself. Get it wrong and you get silently missing data that you won’t notice until your stakeholders ask why the purchase funnel looks broken.
This guide covers every event in the GA4 ecommerce lifecycle, the exact dataLayer structure each one requires, and the GTM configuration that ties it all together.
The ecommerce event lifecycle
Section titled “The ecommerce event lifecycle”GA4’s ecommerce model maps a user’s journey from discovery to purchase. You don’t need to implement every event — but each one you skip is a gap in your funnel visibility.
| Event | When to fire | Required |
|---|---|---|
view_item_list | Product grid, search results | Recommended |
select_item | User clicks a product | Recommended |
view_item | Product detail page | Yes |
add_to_cart | Item added to cart | Yes |
view_cart | Cart page viewed | Recommended |
remove_from_cart | Item removed from cart | Recommended |
begin_checkout | Checkout initiated | Yes |
add_shipping_info | Shipping step completed | Recommended |
add_payment_info | Payment step completed | Recommended |
purchase | Order confirmed | Yes |
refund | Order refunded | Recommended |
The item object structure
Section titled “The item object structure”Every ecommerce event contains an items array. This is the most important part of the entire implementation — every item must include all available parameters, because truncating it now means you lose reporting capability later.
{ item_id: 'SKU-1234', // Required. Your internal product ID. item_name: 'Canvas Sneakers', // Required. Human-readable product name. affiliation: 'Main Store', // Optional. Store name for marketplace tracking. coupon: 'SUMMER20', // Optional. Coupon applied to this item. currency: 'USD', // Optional at item level; required at event level. discount: 5.00, // Optional. Discount amount applied. index: 0, // Position in the list (0-indexed). item_brand: 'Stride', // Brand name. item_category: 'Footwear', // Primary category. item_category2: 'Sneakers', // Sub-category. item_category3: 'Canvas', // Further sub-category. item_category4: 'Low Top', // Even further. item_list_id: 'homepage_recs', // The list this item came from. item_list_name: 'Homepage Recommendations', item_variant: 'White / Size 10', price: 79.99, // Unit price. Must be a number, not a string. quantity: 1 // Must be a number, not a string.}The ecommerce null pattern
Section titled “The ecommerce null pattern”Before every single ecommerce push, clear the previous ecommerce data:
// Step 1: Always clear firstwindow.dataLayer = window.dataLayer || [];window.dataLayer.push({ ecommerce: null });
// Step 2: Then push your eventwindow.dataLayer.push({ event: 'view_item', ecommerce: { currency: 'USD', value: 79.99, items: [{ /* ... */ }] }});This is non-negotiable. GTM’s dataLayer uses a recursive merge model — without clearing, data from a previous add_to_cart event can contaminate your purchase event. If you’ve ever seen a purchase event with the wrong items, this is why.
Event implementations
Section titled “Event implementations”view_item_list
Section titled “view_item_list”Fire this when users see a grid of products — category pages, search results, recommendation carousels.
window.dataLayer.push({ ecommerce: null });window.dataLayer.push({ event: 'view_item_list', ecommerce: { item_list_id: 'category_womens_tops', item_list_name: "Women's Tops", items: [ { item_id: 'SKU-1001', item_name: 'Linen Button Shirt', item_brand: 'Strand', item_category: 'Clothing', item_category2: "Women's", item_category3: 'Tops', item_list_id: 'category_womens_tops', item_list_name: "Women's Tops", index: 0, price: 89.00, quantity: 1 }, { item_id: 'SKU-1002', item_name: 'Silk Blouse', item_brand: 'Strand', item_category: 'Clothing', item_category2: "Women's", item_category3: 'Tops', item_list_id: 'category_womens_tops', item_list_name: "Women's Tops", index: 1, price: 129.00, quantity: 1 } ] }});view_item
Section titled “view_item”Fire on the product detail page. This is the most important single event for funnel analysis.
window.dataLayer.push({ ecommerce: null });window.dataLayer.push({ event: 'view_item', ecommerce: { currency: 'USD', value: 89.00, items: [ { item_id: 'SKU-1001', item_name: 'Linen Button Shirt', item_brand: 'Strand', item_category: 'Clothing', item_category2: "Women's", item_category3: 'Tops', item_list_id: 'category_womens_tops', item_list_name: "Women's Tops", item_variant: 'White / Size M', price: 89.00, quantity: 1 } ] }});add_to_cart
Section titled “add_to_cart”Fire immediately when the user adds an item — not on page load, not on cart page view.
window.dataLayer.push({ ecommerce: null });window.dataLayer.push({ event: 'add_to_cart', ecommerce: { currency: 'USD', value: 89.00, // price × quantity items: [ { item_id: 'SKU-1001', item_name: 'Linen Button Shirt', item_brand: 'Strand', item_category: 'Clothing', item_variant: 'White / Size M', price: 89.00, quantity: 1 } ] }});purchase
Section titled “purchase”The most critical event. Include every item in the order, and match the transaction_id exactly to your order management system.
window.dataLayer.push({ ecommerce: null });window.dataLayer.push({ event: 'purchase', ecommerce: { transaction_id: 'ORDER-7829', // Your order ID. Must be unique. currency: 'USD', value: 164.00, // Revenue after discounts, before tax/shipping. tax: 13.12, shipping: 8.99, coupon: 'SUMMER20', items: [ { item_id: 'SKU-1001', item_name: 'Linen Button Shirt', item_brand: 'Strand', item_category: 'Clothing', item_variant: 'White / Size M', price: 89.00, quantity: 1, coupon: 'SUMMER20', discount: 14.00 }, { item_id: 'SKU-0892', item_name: 'Canvas Tote Bag', item_brand: 'Strand', item_category: 'Accessories', price: 75.00, quantity: 1 } ] }});TypeScript types for ecommerce dataLayer
Section titled “TypeScript types for ecommerce dataLayer”If your team uses TypeScript, type the dataLayer contract and enforce it at compile time:
interface GA4EcommerceItem { item_id: string; item_name: string; affiliation?: string; coupon?: string; currency?: string; discount?: number; index?: number; item_brand?: string; item_category?: string; item_category2?: string; item_category3?: string; item_category4?: string; item_list_id?: string; item_list_name?: string; item_variant?: string; price?: number; quantity?: number;}
interface GA4EcommerceEvent { event: string; ecommerce: { currency?: string; value?: number; transaction_id?: string; tax?: number; shipping?: number; coupon?: string; item_list_id?: string; item_list_name?: string; items: GA4EcommerceItem[]; };}
declare global { interface Window { dataLayer: (GA4EcommerceEvent | { ecommerce: null } | Record<string, unknown>)[]; }}GTM configuration
Section titled “GTM configuration”GA4 Event tag with Send Ecommerce Data
Section titled “GA4 Event tag with Send Ecommerce Data”You need one GA4 Event tag per ecommerce event type, each with the “Send Ecommerce Data” checkbox enabled:
GA4 - Ecommerce - purchase
- Type
- Google Analytics: GA4 Event
- Trigger
- CE - purchase
- Variables
-
Google Tag - Measurement ID
In the tag configuration:
- Set Event Name to
purchase(or use{{Event}}to read the event name from the dataLayer) - Under More Settings → Ecommerce, check Send Ecommerce Data
- Set Data Source to Data Layer
With this configuration, GTM reads the ecommerce object from the dataLayer automatically and sends it to GA4.
Reusable approach: one tag for all ecommerce events
Section titled “Reusable approach: one tag for all ecommerce events”Instead of creating a separate tag for each event, use {{Event}} as the event name and set conditions on your trigger:
-
Create a Custom Event trigger that matches a regex covering all ecommerce events:
^(view_item_list|select_item|view_item|add_to_cart|view_cart|remove_from_cart|begin_checkout|add_shipping_info|add_payment_info|purchase|refund)$ -
Create one GA4 Event tag with Event Name =
{{Event}}and Send Ecommerce Data enabled. -
Attach the single trigger. All ecommerce events now fire through one tag.
This keeps your container clean. One tag, one trigger, all ecommerce events handled.
Common mistakes
Section titled “Common mistakes”Not clearing ecommerce between events
Section titled “Not clearing ecommerce between events”Every practitioner learns this the hard way. Push { ecommerce: null } before every ecommerce push. No exceptions.
String prices and quantities
Section titled “String prices and quantities”// ❌ This will silently fail or produce wrong data in GA4items: [{ item_id: 'SKU-1', price: '89.00', quantity: '1' }]
// ✅ Numbers onlyitems: [{ item_id: 'SKU-1', price: 89.00, quantity: 1 }]Missing item_id
Section titled “Missing item_id”GA4 cannot merge ecommerce data across events without item_id. Without it, you lose the ability to trace a product from impression to purchase. item_id and item_name are the two non-negotiable fields.
Wrong value field
Section titled “Wrong value field”value at the event level should equal the sum of (price - discount) × quantity for all items in that specific interaction — not the cart total, not the catalog price.
Abbreviated items arrays
Section titled “Abbreviated items arrays”Never send a truncated items array in production. Some teams only send the first item for performance reasons. You lose all multi-item analytics.
Forgetting the Send Ecommerce Data checkbox
Section titled “Forgetting the Send Ecommerce Data checkbox”The most common GTM configuration mistake. Your dataLayer push can be perfect, but if the GA4 Event tag doesn’t have “Send Ecommerce Data” checked, none of the ecommerce data reaches GA4.
Combining Send Ecommerce Data with manual parameter mapping
Section titled “Combining Send Ecommerce Data with manual parameter mapping”If you check “Send Ecommerce Data” AND also manually map items as event parameters in the same GA4 Event tag, the manual parameters override the ecommerce struct. This causes confusing behavior — some ecommerce data appears, some doesn’t.
Pick one approach:
- Send Ecommerce Data (recommended for standard ecommerce) — let GTM read the
ecommerceobject from the dataLayer automatically - Manual parameter mapping — explicitly map each parameter in the tag configuration
Never combine both in the same tag. If you need custom parameters alongside ecommerce data, add them as separate event parameters while still using “Send Ecommerce Data” for the ecommerce object.
Verifying your implementation
Section titled “Verifying your implementation”-
GA4 DebugView — Log in to GA4, go to Configure → DebugView. Trigger events on your site with GTM Preview mode active. You should see each event with its ecommerce parameters within seconds.
-
GTM Preview → Data Layer tab — After each user action, click on the event in the Tag Assistant panel and check the Data Layer tab. Confirm the
ecommerceobject contains the expected structure. -
Network tab — Filter for
collectrequests towww.google-analytics.comoranalytics.google.com. Inspect theep.itemsandprparameters in the request payload.