Ecommerce product analytics answers a question that Shopify’s standard reports handle badly: what happens to each product between the moment someone sees it and the moment the order is kept or refunded. Store-level sessions and orders tell you whether the business is healthy. Product-level views, add-to-carts, purchases and refunds tell you which products deserve their place on a collection page and which ones quietly cost you money. This guide builds Shopify product analytics from the event layer up.
What does ecommerce product analytics measure that Shopify’s reports don’t?
The unit of measurement is the product, or more exactly the variant, followed through a fixed set of moments. It appears in a list, gets clicked, has its page viewed, is added to a cart, and is then bought or refunded. Each moment is an event carrying the same identifier. When that identifier is consistent, you can divide any moment by an earlier one and get a rate per product.
Shopify’s admin reports are built mostly from orders and sessions. They tell you what sold. A view is not an order, so on their own they say little about the people who looked at a product and left. Shopify has been adding product-level traffic and conversion reporting, and what you see depends on your plan, so check which product reports your admin exposes before building anything. The store-level view is covered in Shopify analytics.
Google Analytics 4 records the browsing side, but its revenue is only as good as the events you send it. It knows nothing about refunds unless you tell it, and it knows nothing about your cost of goods. Neither tool alone gives you contribution by product. The setup in this guide joins the two, and Google Analytics on Shopify covers the property-level configuration this guide assumes is done.
Who is this for, and what do you need before starting?
This guide is written for operators running $3M+ in revenue on Shopify Plus or a paid subscription platform, where merchandising, paid media and retention all argue from product numbers. It is not for brands below that floor, and it is not for teams shopping for a tool to buy. It assumes you will wire events yourself or hand the spec to a developer.
| Prerequisite | Why you need it |
|---|---|
| Shopify admin access to Settings, Customer events | Custom pixels are created and edited there |
| A GA4 property with Editor access | Custom definitions and data retention live in Admin |
| A developer for a few hours of pixel code | The mapping in Step 3 is JavaScript, not configuration |
| Order and refund export access | Refunds and cost per item come from Shopify, not GA4 |
| A decision on the product identifier | Every later step depends on it |
Read the table as a checklist: the last row is the only one that needs a meeting rather than a login.
One more filter. Product-level rates only settle when each product gets enough views for a rate to mean something. If a slow-moving product gets a handful of views, judge it on orders, margin and refunds, and leave its conversion rate out of decisions. The FAQ covers how to find that cut-off for yourself.
How do you set up Shopify product analytics, step by step?
The seven steps run in a fixed order because each one depends on the identifier and event names chosen before it. Skipping to dashboards is the most common way to build a report that has to be rebuilt.
Step 1: Choose one product identifier and use it everywhere
Four systems will each try to give a product a different name: Shopify, GA4, your Google Merchant Center feed, and whoever owns the warehouse or ERP. Choosing the key once, in writing, before any code, prevents the most common failure in this kind of setup.
| Identifier | Stable when someone edits the product? | Level | Use it as |
|---|---|---|---|
| Shopify product ID | Yes | Product | item_id |
| Shopify variant ID | Yes | Variant | item_variant |
| SKU | No, ops teams rename and merge them | Variant, if filled in | A separate item-scoped dimension, never the key |
| Merchant Center feed ID | Set by the feed app, often a composite | Variant | Kept in a mapping table |
| Product handle | No, it changes on rename | Product | Never |
The takeaway from the table is that only Shopify’s own numeric IDs survive editing, so they are the only safe join keys.
My preference is the product ID for item_id, because merchandising judges products, not sizes, and GA4’s item reports then roll variants up for free. The variant ID rides along in item_variant so you can still split by size or colour. Check what ID format your feed app writes into Merchant Center; some apps build a composite string, and that string belongs in the mapping table, not in GA4.
Write the choice on a single page, the key sheet: which field is the join key, which fields are labels, and who is allowed to change either. Send it to everyone who touches analytics, the feed or the warehouse.
Step 2: Decide which system fires each event
Every event name gets exactly one owner. Most double-counting comes from two sources firing the same event: a theme-level Google Tag Manager container, a Google app connected to the store, and a custom pixel all sending purchase to one GA4 property.
For most stores the cleanest design is one custom pixel, created under Settings, Customer events, that subscribes to Shopify’s standard events and sends everything to GA4. One code path has one place to break. The pixel runs in a sandbox, so a tag manager container placed in the theme cannot see checkout events on stores using Shopify’s current checkout; see Shopify checkout extensibility for what that changes, and check Shopify’s documentation for what your checkout permits.
One gap needs a workaround. Shopify has no standard event for a shopper clicking a product tile in a list, so select_item cannot come from the pixel alone. The theme can publish a custom event that the pixel subscribes to; confirm the current call in Shopify’s Web Pixels documentation before you write it.
| Event | Owner |
|---|---|
view_item_list, view_item, add_to_cart, remove_from_cart, view_cart | Custom pixel |
select_item | Theme publishes, custom pixel sends |
begin_checkout, add_shipping_info, add_payment_info, purchase | Custom pixel |
refund | Server or warehouse, never the browser |
After you assign owners, list every other tag, app and connected channel that can send to the same GA4 property and switch off the overlaps. Do this now; deduplicating afterwards means reconstructing which source produced which row.
Step 3: Map Shopify’s standard events to GA4 item events
Shopify’s standard events and GA4’s recommended ecommerce events describe the same moments in different words. The pixel’s job is translation.
| Shopify standard event | GA4 event |
|---|---|
collection_viewed | view_item_list |
| (theme click, see Step 2) | select_item |
product_viewed | view_item |
product_added_to_cart | add_to_cart |
product_removed_from_cart | remove_from_cart |
cart_viewed | view_cart |
checkout_started | begin_checkout |
checkout_shipping_info_submitted | add_shipping_info |
payment_info_submitted | add_payment_info |
checkout_completed | purchase |
Use the table as the spec for the pixel: one subscription per row, each sending an items array. These are the settings that go wrong.
item_id: Shopify’s numeric product ID, as a string, identical in every event.item_variant: the numeric variant ID, so the split by size or colour exists.item_name,item_brand: product title and vendor. Takeitem_categoryfrom product type only after cleaning it, because product type is free text and “Tees”, “T-shirts” and “tee” will otherwise be three categories.priceandquantity: decide whetherpriceis pre- or post-discount and stay consistent. Shopify’s checkout exposes both figures.currency: the ISO currency code, on every event that carriesvalue. If you sell in several currencies through Shopify Markets, report in one currency and note which.value: the sum of price times quantity across the items. Shipping and tax do not belong here; GA4 has separateshippingandtaxparameters.transaction_id: Shopify’s numeric order ID, not the display name such as #1001, so the join to an order export works.item_list_idanditem_list_name: the collection handle and title on list events, so you can tell a homepage carousel from a collection grid.
Step 4: Register item-scoped dimensions and lengthen retention
GA4 only reports on custom parameters once you register them, and only from the day you register them. Open Admin, then Custom definitions, then Create custom dimension, and set the scope to Item. Register the extra item fields you send: SKU, variant ID, cleaned product type, and a bundle flag. Nothing back-fills, so do this before launch, not after the first month of data.
Then open Admin, Data collection and modification, Data retention, and set event data retention to the longest option available. Retention limits how far back Explorations can look, which is where product tables live. Standard reports are unaffected, so the default can seem harmless until someone asks for a year-on-year product comparison.
Finally, link the property to BigQuery under Admin, Product links, BigQuery links. The export keeps raw event rows, including the items array, independent of the reporting interface. Even if nobody queries it today, it is the cheapest insurance against a question you haven’t thought of yet.
Step 5: Send refunds back or join them outside GA4
Refunds are where product analytics usually stops being honest. A product with a strong add-to-cart rate and a poor refund rate looks like a hero in GA4 alone.
There are two routes. The first is to send GA4’s refund event through the Measurement Protocol, triggered by a Shopify refund webhook. It needs the original transaction_id, and for partial refunds the items refunded. The Measurement Protocol also needs a client_id to attach the event to the right user, which means capturing the GA client ID at checkout and storing it on the order, for example as a cart attribute. That is the hidden cost: it turns a tracking task into a small integration you must maintain.
Joining outside GA4 skips the webhook entirely. Export Shopify’s orders and refunds with line items, and join them to the GA4 product table by product ID in a spreadsheet or warehouse. I recommend the second route for most teams. GA4 stays the source for behaviour, Shopify stays the source for money, and nobody has to maintain a webhook.
Step 6: Build the product table
In GA4, open Explore, start a Free form exploration, and use Item ID and Item name as row dimensions. Add the metrics Items viewed, Items added to cart, Items purchased and Item revenue. Metric names change occasionally, so check what your property offers. Then bring in refunds and cost per item from Shopify, which stores a cost per item field on each variant, to reach net revenue and margin.
The columns to build, in order:
- Views, add-to-carts, purchases: raw counts from GA4.
- View-to-cart rate: add-to-carts divided by views.
- Cart-to-purchase rate: purchases divided by add-to-carts.
- Revenue per view: item revenue divided by views, the fairest way to compare a cheap, popular product with an expensive, rarer one.
- Refund rate: refunded units divided by units sold, from Shopify.
- Net margin: item revenue, minus refunds, minus cost per item times units.
Take an illustrative, hypothetical product with 4,000 views, 240 add-to-carts and 48 purchases. In this illustration the view-to-cart rate works out at 6% and the cart-to-purchase rate at 20%. Those two rates sit side by side because they diagnose different problems: a weak first rate points at the product page, a weak second points at price, shipping cost or checkout friction. The numbers here are illustrative and not a benchmark; for context on store-wide rates, see average ecommerce conversion rate by industry.
Step 7: Reconcile against Shopify orders
The reconciliation check is the step that lets anyone trust the rest. Choose a fixed date range, pull the distinct transaction_id values from GA4 and the order IDs from Shopify, and compare the two lists.
Set both systems to the same time zone first. GA4’s reporting time zone lives in the property’s settings and Shopify’s in the store’s general settings; a mismatch shifts orders across midnight and creates a phantom gap on every daily comparison.
Then compute the gap as Shopify orders minus GA4 transactions, divided by Shopify orders. Record it as your baseline. A gap is expected, since consent choices, ad blockers, draft orders, point-of-sale sales and subscription renewals never reach a browser pixel. What matters is the gap staying stable. Your acceptable gap is metric to confirm for your own store, not a number anyone can quote for you.
Run the comparison weekly for the first month, and again after any theme, checkout or app change.
Why do most Shopify product analytics setups fail at the identifier?
Most failed setups die the same quiet death. A merchandiser pastes a Merchant Center report into one tab, where the ID is a composite string from the feed app. A GA4 export sits in another, keyed on variant IDs because a developer took variant.id from the first example they found. Finance holds a third tab, keyed on SKU. Nothing joins, so someone builds a lookup by hand, and it is out of date within a month.
The subtler version happens inside GA4. If view_item sends the product ID but purchase sends the variant ID, the same product appears as two items. One has views and no purchases, the other has purchases and no views. Every rate becomes nonsense, and nothing errors, because GA4 accepts any string in item_id.
A quick diagnostic catches it. In an Exploration, filter to items with Items purchased above zero and Items viewed equal to zero. A healthy setup returns almost nothing, since a bought product was nearly always viewed first. A broken one returns a long list, and that list is the evidence for fixing the key sheet.
Shipping and tax inside value is a common mistake of its own. Revenue then looks higher than Shopify’s product sales, and the difference becomes an argument about whose number is right, when the parameter was simply misused.
How do you verify the setup before trusting it?
Verification takes an afternoon and saves months of arguing about numbers. Place three real test orders: a single item, a two-item cart with a discount code, and one containing a bundle or subscription product if you sell them. Watch each in GA4’s DebugView, enabled through GA4’s debug mode or Google Tag Manager’s preview tool.
| Test | Pass looks like | Failure means |
|---|---|---|
| Two-item order in DebugView | One purchase, an items array with two entries, right IDs | Double-firing or a mapping bug |
| Same order in Shopify | transaction_id equals the Shopify order ID | The join to exports will fail |
| Discounted order | value and Shopify’s product sales agree, and the discount is treated the way you decided | Price handling is inconsistent |
| Refund a test order | The refund shows in your chosen route | Refund plumbing is missing |
| Items in GA4 Explore | No “(not set)” item names, no items with purchases and zero views | The identifier is still split |
| Distinct transaction IDs against total purchase events | The counts match | Duplicate purchases are being counted |
Take from the table which failure maps to which fix: every row failing points to one of the seven steps, not to “analytics is broken”.
Once the checks pass, keep the three test orders documented, with dates and IDs. When someone asks in six months whether the numbers can be trusted, you can re-run the same tests and answer with evidence.
What breaks as volume and catalogue grow?
Growth stresses this setup in predictable places. Knowing them in advance is cheaper than discovering them from a wrong dashboard.
Explorations hit limits. GA4 applies quotas and sampling to large Explorations, and it caps the unique values a dimension can report before rolling the rest into an “(other)” row. A large catalogue with item-scoped dimensions of many unique values can hit that cap. Check Google’s current documentation for the limits, and move product-level work to the BigQuery export when the interface stops being reliable.
Bundles blur the unit. Some bundle apps create a parent product with child line items; others discount the components at checkout. Decide whether the bundle or its parts is the reporting unit, and test one of each type. See Shopify bundle app for how the apps differ.
Renewals are invisible. A subscription renewal is created by the subscription platform, so the browser never sees it. Product analytics built only on pixels will undercount repeat revenue from subscription products, and the reconciliation gap will widen as the subscription base grows. Take renewals from the platform’s data and label them separately.
Catalogue churn leaves ghosts. Discontinued products keep old IDs in historical data. Keep a status column in the key sheet so a table doesn’t treat a retired product’s low recent views as a problem to fix.
Consent shapes the browsing side. What you may collect, and under which consent settings, differs by market. Confirm specifics with counsel. From the analytics side, the reconciliation gap is your measure of how much of the browsing picture is missing.
Do you need Triple Whale, Northbeam or a warehouse instead?
Not before this setup works. Attribution platforms such as Triple Whale and Northbeam combine ad-platform spend with order data and are built around marketing attribution. Both publish their packaging on their own pricing pages; check whether price scales with revenue, order volume or ad spend, and whether the plan you need includes the product-level views you want. No number here would survive a week, so none is quoted.
Whatever you buy, these tools read the same underlying data. If your product identifier is split, a paid dashboard displays the split more attractively, but it does not repair it. The order I’d follow is a trusted free stack first, then a tool if a question it answers is worth its cost. That is a less exciting recommendation than a vendor would give.
If the decision is which reporting layer to add, Shopify reporting apps and Shopify dashboards cover the options. A warehouse becomes the right home once refunds, cost, subscription renewals and ad spend all need to sit in one place, and once several people query the same numbers.
Product analytics that nobody trusts is a Reporting & analytics problem, not a tooling problem: one identifier, one owner per event, and a reconciliation that runs every week. If your team is arguing about whose product numbers are right, Pointerflow’s reporting and analytics service starts with exactly this setup: the key sheet, the event map, and the reconciliation baseline.
Sources
- No external figures are quoted. The article is written from Shopify’s standard customer events (Web Pixels) and Google Analytics 4’s recommended ecommerce events and item parameters, as documented by each vendor. Check both vendors’ documentation for current event, metric and limit details.