Analytics
A data layer specification for ecommerce stores
A written data layer spec keeps GA4, Google Ads, Meta and your developers on the same event names, item fields and value rules. The template, field by field.
By CartKernel · Published
A data layer specification is one document that says, for every event on the store, what triggers it, what it is called, and what it carries. Without one, GA4, Google Ads, Meta and the developers each decide for themselves, and the disagreements surface later as revenue that does not match, conversions that do not fire, and a return on ad spend that means something different in every report. This is the structure of the spec we write for stores, with the decisions inside it explained.
What the document contains
- Conventions: names, currency, the value definition, privacy rules.
- A page context object present on every page.
- The event table: trigger, name, parameters.
- The item object, defined once and reused.
- Platform notes: how it is implemented on Shopify, WooCommerce or a headless front end.
- A test matrix and a version log.
Six sections, usually eight to twelve pages. It lives in the repository next to the theme, and every theme or checkout change is reviewed against it.
Conventions
Use GA4’s recommended event names. view_item, add_to_cart, purchase and the rest exist so the platform’s reports and Google Ads conversion imports understand the events without mapping. Invent a name only where no recommended event fits, and write it in snake_case. The recommended events reference lists the parameters each one expects.
Currency on every event that carries a value. An ISO code, taken from the checkout currency, not the store default. Stores selling in several presentment currencies get this wrong most often.
Decide what value means and never vary it. Our default for purchase is the order subtotal after discounts, before tax and shipping, with tax and shipping sent as their own parameters. The reason is comparability: Google Ads and Meta receive the same value, so return on ad spend means the same thing in both, and the number can be checked against the Shopify export column of the same name. A store can choose a different definition, but it must be one definition, written in the spec.
Clear the ecommerce object between pushes. On tag manager implementations, push { ecommerce: null } before each ecommerce event so item arrays from the previous event do not merge into the next. Google’s data layer documentation explains why.
No personal data in the clear. Email and phone for enhanced conversions are either hashed before they reach the data layer or handed to the tag that hashes them, and only when the consent state allows it. The enhanced conversions entry describes what Google accepts, and how to set up enhanced conversions for Shopify covers the platform specifics.
The page context object
Pushed once per page before any tag fires, so every tag can read it.
| Key | Values | Used for |
|---|---|---|
page_type |
home, collection, product, cart, checkout, search, account, blog, other | Template-level reporting and trigger conditions |
currency |
ISO code of the active checkout currency | Value handling |
market |
Country or market code | Regional reports and consent defaults |
language |
Language code | Localised reporting |
customer_status |
guest, new, returning | New versus returning revenue |
customer_id |
Hashed identifier when logged in | User-ID reporting |
cart_value, cart_item_count |
Numbers | Remarketing audiences |
consent |
analytics and ads booleans, when the consent platform exposes them | Debugging Consent Mode |
customer_status needs a rule: a customer is “returning” when the order count on the account is above zero at the time the page loads. That rule feeds how you track new versus returning customer revenue, so write it down.
The event table
| Event | Fires when | Required parameters | Items |
|---|---|---|---|
view_item_list |
A collection, search results or recommendation block renders | item_list_id, item_list_name |
Visible products with index |
select_item |
A product card is clicked | item_list_id, item_list_name |
The one product |
view_item |
A product page loads | currency, value |
The product with selected variant |
add_to_cart |
The add button succeeds | currency, value |
Added item and quantity |
remove_from_cart |
Quantity reduced or line removed | currency, value |
Removed item and quantity |
view_cart |
Cart page or drawer opens | currency, value |
Every line |
begin_checkout |
Checkout page loads | currency, value, coupon |
Every line |
add_shipping_info |
Shipping method chosen | shipping_tier |
Every line |
add_payment_info |
Payment method chosen | payment_type |
Every line |
purchase |
The order is confirmed | transaction_id, currency, value, tax, shipping, coupon |
Every line |
refund |
A refund is issued, sent server-side | transaction_id, value |
Refunded lines |
search |
Site search submitted | search_term |
none |
sign_up, login |
Account created or session started | method |
none |
generate_lead |
Newsletter or SMS signup | lead_source |
none |
One row per event, with the trigger described in terms a developer can implement and a tester can reproduce. “The add button succeeds” means after the cart API returns, not on click.
The item object
Defined once, reused in every items array.
item_id: the identifier your Merchant Center feed uses as its offer ID, so Shopping reports and analytics agree on what sold. Put the SKU in a separateitem_skuparameter if you need it.item_name: the product title without the variant.item_variant: the variant title, for example “Large / Navy”.item_brand: the vendor field.item_categorythroughitem_category5: the collection or category path, from the primary category.price: the unit price after line-level discounts, in the event currency.quantity: the line quantity.discount: the per-unit discount applied, when any.index,item_list_id,item_list_name: where the item was seen, carried through to purchase when available.
Rule: the sum of price times quantity across the items must equal the event value, given the value definition above. When they differ, item revenue and event revenue in GA4 disagree and nobody can say which is right.
A purchase push under these rules looks like this:
{
"event": "purchase",
"ecommerce": {
"transaction_id": "1042",
"currency": "CAD",
"value": 148.00,
"tax": 19.24,
"shipping": 12.00,
"coupon": "WELCOME",
"items": [
{
"item_id": "shopify_CA_812_3301",
"item_name": "Trail Jacket",
"item_variant": "Large / Navy",
"item_brand": "Northline",
"item_category": "Outerwear",
"item_category2": "Jackets",
"price": 148.00,
"quantity": 1
}
]
}
}
The values are illustrative; the shape is the point.
Platform notes
Shopify. The checkout cannot run theme code, so checkout events come from the Web Pixels API. A custom pixel subscribes to the standard events, checkout_started, checkout_completed and the others, and forwards them in the GA4 shape above. The cleanest implementation runs the whole funnel through the pixel, including product and collection views, so nothing is counted once by the theme and again by the pixel. If the Google and YouTube channel app is also connected, decide which path sends GA4 events and switch off the other. The standard events and their payloads are documented in the Web Pixels API reference.
WooCommerce. A tag manager plugin or a small custom plugin pushes the data layer on each template, with checkout events hooked into the checkout block and the purchase on the order-received page guarded against refresh with a flag stored per order.
Headless. The front end owns the client pushes; the server sends purchase and refund through the Measurement Protocol using the same transaction_id, so GA4 deduplicates the pair. The spec marks each event as client, server or both.
Whichever platform, the spec names the dedupe key and the owner of each event. Server-side tagging describes the routing once collection moves off the browser, and does an ecommerce store need server-side tagging helps decide whether it should.
The test matrix
Run before every release that touches the theme, checkout or tags, with GA4 DebugView and the tag manager preview open.
- One product from each template variation, with a variant selected and without.
- Add, change quantity, remove, from the product page and from the cart drawer.
- A discount code, a free-shipping order, a multi-currency order.
- Guest checkout and logged-in checkout.
- Refresh and back button on the thank-you page: exactly one purchase.
- Compare one day’s purchase events with Shopify’s order count, which is the short form of the reconciliation we run monthly.
When the purchase row fails, Google Ads conversion tracking not working and GA4 purchase event not firing cover the common causes.
Versioning and ownership
Give the spec a version number and a changelog. Every pull request that touches tracking references the version it implements. Name one owner, usually whoever owns analytics, and one developer who reviews tracking changes. The data layer entry is the short definition for stakeholders who need the concept without the document; the GA4 ecommerce tracking and ecommerce development services are where we write and implement it for stores.