To track product sales, views, and site search in Google Analytics 4 (GA4) you send recommended ecommerce events with gtag.js — or push them to the dataLayer for Google Tag Manager. Each event (view_item, add_to_cart, begin_checkout, purchase, and so on) carries a standardized items array plus fields such as value, currency, and transaction_id.
The old Universal Analytics Enhanced Ecommerce plugin — analytics.js loading ec.js, with calls like ga('ec:addProduct') and ga('ec:setAction') — stopped collecting data when Universal Analytics was shut down on 1 July 2023. If your store still ships that code, it is recording nothing. The fix is to migrate every action to its GA4 event equivalent.
Key takeaways
- Enhanced Ecommerce (ec.js) is dead. Universal Analytics stopped processing hits on 1 July 2023, so
ga('ec:*')calls collect no data. - GA4 uses recommended events, not a plugin. You fire named events (
view_item,add_to_cart,purchase,search) throughgtag.jsor thedataLayer. - One shared
itemsarray describes products across every event, usingitem_id,item_name,price,quantity, and friends. purchaseneeds a uniquetransaction_idplusvalueandcurrencyso revenue is counted exactly once.- Site search is now an event (
searchwithsearch_term) instead of a view-level setting. - Server-side is optional but useful — the GA4 Measurement Protocol or a server-side GTM container captures payments confirmed by webhook and resists ad-blockers.
Why has Enhanced Ecommerce (ec.js) stopped working?
Enhanced Ecommerce was a plugin for Universal Analytics. You loaded it with ga('require', 'ec') after creating the tracker, described products with ga('ec:addProduct', {...}), and described what happened with ga('ec:setAction', 'detail' | 'add' | 'purchase', {...}). A typical purchase looked like this in the old world:
// DEPRECATED - Universal Analytics Enhanced Ecommerce. This no longer collects data.
ga('require', 'ec');
ga('ec:addProduct', {
id: 'E60152',
name: 'Sandisk Pen Drive',
category: 'Electronics',
brand: 'Sandisk',
variant: '32GB',
});
ga('set', '&cu', 'EUR');
ga('ec:setAction', 'purchase', {
id: '1054412477',
revenue: '37.39',
});
ga('send', 'pageview');How do GA4 events map to the old Enhanced Ecommerce actions?
Because Universal Analytics no longer accepts data, those ec:* calls do nothing today. Do not patch them — replace each Enhanced Ecommerce action with the equivalent GA4 recommended event:
| Universal Analytics (Enhanced Ecommerce) | GA4 recommended event |
|---|---|
ec:addImpression |
view_item_list |
ec:addProduct + ec:setAction('click') |
select_item |
ec:addProduct + ec:setAction('detail') |
view_item |
ec:setAction('add') |
add_to_cart |
ec:setAction('remove') |
remove_from_cart |
ec:setAction('checkout') (step 1) |
begin_checkout |
ec:setAction('checkout') (payment step) |
add_payment_info |
ec:setAction('purchase') |
purchase |
ec:setAction('refund') |
refund |
ec:addPromo |
view_promotion / select_promotion |
| Site Search view setting | search / view_search_results |
What does the GA4 items array contain?
Every ecommerce event shares the same items array. Each entry is one product line; the most useful fields are:
| Field | Type | Example | Notes |
|---|---|---|---|
item_id |
string | E60152 |
SKU or product ID (supply this or item_name) |
item_name |
string | Sandisk Pen Drive |
product name (supply this or item_id) |
price |
number | 12.99 |
unit price, no currency symbol |
quantity |
number | 2 |
units on this line |
item_brand |
string | Sandisk |
brand |
item_category |
string | Electronics |
top category (through item_category5) |
item_variant |
string | 32GB |
variant or option |
item_list_name |
string | Search Results |
the list it appeared in |
index |
number | 1 |
position within that list |
coupon |
string | WELCOME10 |
item-level promo code |
discount |
number | 2.00 |
per-unit discount amount |
At the event level you also send currency (an ISO-4217 code such as EUR), value (the monetary total), and — for orders — transaction_id, tax, and shipping.
How do you track a product view with gtag.js?
Fire view_item when a shopper opens a product detail page. Send the product in the items array along with value and currency so GA4 can report potential revenue:
// Fired when a shopper opens a product detail page.
gtag('event', 'view_item', {
currency: 'EUR',
value: 12.99,
items: [
{
item_id: 'E60152',
item_name: 'Sandisk Pen Drive',
item_brand: 'Sandisk',
item_category: 'Electronics',
item_variant: '32GB On-The-Go',
price: 12.99,
quantity: 1,
},
],
});For listings — search results, a category grid, a "related products" rail — send view_item_list for the impressions and select_item when the shopper clicks one to open it. Keep item_list_id/item_list_name identical across both so GA4 can attribute the click to the list:
// Impressions: the products a shopper sees in a list.
gtag('event', 'view_item_list', {
item_list_id: 'search_results',
item_list_name: 'Search Results',
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', index: 1, price: 12.99 },
{ item_id: 'H25482', item_name: 'Skullcandy Headset', index: 2, price: 24.5 },
],
});
// The shopper clicks one result to open it.
gtag('event', 'select_item', {
item_list_id: 'search_results',
item_list_name: 'Search Results',
items: [{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', index: 1 }],
});How do you track add-to-cart and checkout?
As the shopper moves toward buying, fire add_to_cart, then begin_checkout, then add_payment_info. Keep the items, value, and currency consistent so the funnel reports line up step to step:
gtag('event', 'add_to_cart', {
currency: 'EUR',
value: 25.98,
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', price: 12.99, quantity: 2 },
],
});
gtag('event', 'begin_checkout', {
currency: 'EUR',
value: 25.98,
coupon: 'WELCOME10',
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', price: 12.99, quantity: 2 },
],
});
gtag('event', 'add_payment_info', {
currency: 'EUR',
value: 25.98,
payment_type: 'Credit Card',
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', price: 12.99, quantity: 2 },
],
});How do you track a purchase?
The purchase event drives your revenue reports. Fire it once, on the order-confirmation page, with a unique transaction_id. Reusing an ID — or letting the event fire again on a page refresh — is the most common cause of inflated or wrongly de-duplicated revenue:
// Fire once on the order-confirmation page.
// transaction_id MUST be unique per order so GA4 can de-duplicate refreshes.
gtag('event', 'purchase', {
transaction_id: '1054412477',
currency: 'EUR',
value: 37.39,
tax: 2.39,
shipping: 4.0,
coupon: 'WELCOME10',
items: [
{
item_id: 'E60152',
item_name: 'Sandisk Pen Drive',
item_brand: 'Sandisk',
item_category: 'Electronics',
price: 12.99,
quantity: 2,
},
{
item_id: 'H25482',
item_name: 'Skullcandy Headset',
item_brand: 'Skullcandy',
item_category: 'Accessories',
item_variant: 'Blue',
price: 11.41,
quantity: 1,
},
],
});How do you track site search in GA4?
In Universal Analytics you enabled Site Search in the View settings and named the query parameter (for example q, for URLs like /search?q=pen+drive). GA4 has no views and no such toggle — you send a search event yourself, passing the visitor's query in the reserved search_term parameter. That parameter is what populates GA4's search reports:
// Site search: fire `search` with the raw query the visitor typed.
gtag('event', 'search', {
search_term: 'pen drive',
});
// On the results page you can also report what was shown, so you can later
// see which queries surface products and which come back empty.
gtag('event', 'view_search_results', {
search_term: 'pen drive',
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', index: 1 },
],
});How do you send the same events through Google Tag Manager?
If you use GTM instead of hard-coded gtag.js, push an event object onto the dataLayer. Clear the previous ecommerce object first so stale items do not leak into the next event:
// Google Tag Manager: clear the previous ecommerce object, then push the event.
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null }); // clear stale items
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'EUR',
value: 25.98,
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', price: 12.99, quantity: 2 },
],
},
});In GTM, create a GA4 Event tag, set the event name from a Data Layer variable (or the literal name), enable Send Ecommerce data with Data Layer as the source, and fire it on a Custom Event trigger matching add_to_cart, purchase, and the rest. GTM reads the ecommerce.items array automatically.
Can you track ecommerce events server-side?
Yes. For revenue that must not be lost to ad-blockers, or that is only confirmed asynchronously (a payment webhook, a back-office refund), send events from your backend with the GA4 Measurement Protocol, or route browser hits through a server-side GTM container. A Measurement Protocol call is a plain HTTPS POST that reuses the GA4 event schema:
// Node.js: send a server-confirmed purchase via the GA4 Measurement Protocol.
const measurementId = 'G-XXXXXXXXXX';
const apiSecret = process.env.GA4_API_SECRET;
await fetch(
`https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`,
{
method: 'POST',
body: JSON.stringify({
client_id: '1234567890.0987654321', // the GA client_id captured in the browser
events: [
{
name: 'purchase',
params: {
transaction_id: '1054412477',
currency: 'EUR',
value: 37.39,
items: [
{ item_id: 'E60152', item_name: 'Sandisk Pen Drive', price: 12.99, quantity: 2 },
],
},
},
],
}),
},
);Server-side tracking pairs naturally with a backend that already owns the order record — a strong fit for our Python development services or a full web development build, where the same purchase payload your API returns can be forwarded straight to GA4.
How do you confirm events and read the reports?
You no longer toggle an "Enhanced Ecommerce" setting — sending the events is enough. To confirm they arrive, open Admin > DebugView (after enabling debug mode with the GA Debugger extension or debug_mode: true), or watch the Realtime report. Reporting then surfaces the data:
- Monetization > Ecommerce purchases — products, revenue, and quantities sold.
- Reports > Engagement and the Search terms report — what visitors searched for.
Once events are flowing, you can visualize them on a custom dashboard — see adding Google Analytics graphs to your dashboard.
Frequently Asked Questions
Is Universal Analytics Enhanced Ecommerce still supported?
No. Universal Analytics stopped processing new data on 1 July 2023, and standard UA properties were deleted afterward. Any ga('ec:*') Enhanced Ecommerce calls now collect nothing. You must move to GA4 recommended ecommerce events sent with gtag.js or Google Tag Manager.
What is the difference between gtag.js and the dataLayer for ecommerce?
gtag.js sends events directly to GA4 from your page code. The dataLayer is the queue Google Tag Manager reads — you dataLayer.push() an event object and a GTM tag forwards it to GA4. Use gtag.js for simple hard-coded setups and the dataLayer when GTM owns your tags. Both carry the identical items array.
Which GA4 events should a typical store send?
For a standard funnel: view_item_list and select_item on listings, view_item on product pages, add_to_cart and remove_from_cart on the cart, begin_checkout and add_payment_info during checkout, purchase on the confirmation page, plus search for site search. Send only the steps your store actually has.
Why is GA4 double-counting my revenue?
Almost always a transaction_id problem. If the purchase event fires on every refresh of the confirmation page, or two orders share an ID, GA4 cannot de-duplicate correctly. Fire purchase exactly once per order with a unique transaction_id, and guard it so a reload does not re-send it.
How do I track site search now that the view setting is gone?
Send a search event with the query in the search_term parameter whenever a search runs; GA4 uses search_term to populate its search reports. You can also send view_search_results (with the items shown) on the results page to analyze which queries return products and which come back empty.
Do I need server-side tracking for GA4 ecommerce?
Not required. Client-side gtag.js or GTM is enough for most stores. Server-side tracking — the Measurement Protocol or a server-side GTM container — adds accuracy when payments are confirmed by webhook, when refunds happen in the back office, or when you want to reduce data lost to ad-blockers. Many teams run both: client events for behavior, server events for confirmed revenue.