Shopify · GTM container configuration
Pushing the dataLayer from a Shopify Custom Pixel, the customer-events mapping table, and GTM's correct role on Shopify.
Important limits (read first)#
Running GTM inside the Shopify Custom Pixel (Web Pixel) sandbox is not supported:
- Since 2025 Shopify removed the ability to paste third-party tags in custom HTML/Scripts; all third-party pixels must use Custom Pixels (sandboxed execution)
- Google's documentation explicitly does not recommend running GTM or Google tags inside the Custom Pixel sandbox — the sandbox delays and drops events, and Google does not support troubleshooting it
- The sandbox cannot access the page DOM: scroll depth, file downloads, outbound clicks, affiliate detection and DOM-based form submission are unavailable in a pure Custom Pixel setup
| Feature | Shopify availability |
|---|---|
| Ecommerce events (view_item / add_to_cart / begin_checkout / purchase…) | Available (Custom Pixel subscribes to customer events and pushes the dataLayer) |
| Form submission (contact / register / subscribe) | Partial (subscribe to form-related customer events) |
| Outbound clicks / affiliate detection | Unavailable (no DOM access in the sandbox) |
| Scroll depth | Unavailable (no DOM access in the sandbox) |
| File downloads | Unavailable (no DOM access in the sandbox) |
Prerequisites#
- Shopify admin access (Settings → Customer events)
- A GA4 measurement ID (G-XXXXXXXXXX)
- A GTM container (for local preview / QA, or as a future GA4 configuration hop)
Configure a Custom Pixel to push the dataLayer#
- Shopify admin → Settings → Customer events
- Click Add custom pixel and name it e.g. metricsKit
- Paste the code and save
import { register } from '@shopify/web-pixels-extension';
register(async (analytics) => {
const browser = analytics.browser;
analytics.subscribeAll((event) => {
switch (event.name) {
case 'payment_info_submitted':
browser.dataLayer.push('event', 'add_payment_info');
break;
case 'checkout_address_info_submitted':
browser.dataLayer.push('event', 'add_shipping_info');
browser.dataLayer.push('event', 'view_item_list');
break;
case 'product_viewed':
browser.dataLayer.push('event', 'view_item', {
ecommerce: { items: [toItem(event.data)] }
});
break;
case 'product_added_to_cart':
browser.dataLayer.push('event', 'add_to_cart', {
ecommerce: { items: [toItem(event.data)] }
});
break;
case 'checkout_started':
browser.dataLayer.push('event', 'begin_checkout');
break;
case 'checkout_completed':
browser.dataLayer.push('event', 'purchase', {
ecommerce: { transaction_id: event.data.checkout.id }
});
break;
}
});
function toItem(d) {
return {
item_id: d.productVariant.id,
item_name: d.productVariant.title,
price: d.productVariant.price.amount,
currency: d.productVariant.price.currencyCode
};
}
});Getting events to GA4#
- Option A (recommended): Shopify's official Google & YouTube app. Install from the App Store and connect your GA4 property — Shopify's only official path since 2025, with complete events and support
- Option B: Measurement Protocol (advanced). Fetch the GA4 Measurement Protocol endpoint directly from the Custom Pixel (requires your own api_secret; for teams with development capacity)
On Shopify the GTM container only serves "preview / local QA": temporarily load the container in the theme to validate event parameters; production collection does not depend on GTM.
GTM container configuration items (Shopify-usable subset)#
If you configure on the GTM side, the container entities match the standard list, but the Shopify sandbox blocks DOM access, so only the ecommerce items apply (the rest are WordPress-only or unavailable):
The complete container configuration is as follows (identical to what the metricsKit "Import file" creates): 2 custom JavaScript variables, 4 data-layer variables, 9 triggers and 10 tags. Every tag is of type "Google Analytics: GA4 Event", using the site's bound GA4 measurement ID.
| Variable name | Type | Configuration / Data Layer name |
|---|---|---|
| metricsKit - Outbound Domain | Custom JavaScript | Compares the clicked link host with the site host to detect outbound clicks |
| mk - Affiliate Detector | Custom JavaScript | Outputs program / affiliateId / affiliateType from your AFF patterns |
| dlv - ecommerce.value | Data Layer Variable | ecommerce.value |
| dlv - ecommerce.currency | Data Layer Variable | ecommerce.currency |
| dlv - ecommerce.transaction_id | Data Layer Variable | ecommerce.transaction_id |
| dlv - ecommerce.items | Data Layer Variable | ecommerce.items |
| Trigger name | Type | Configuration |
|---|---|---|
| metricsKit - Form Submission | Form Submission | All forms |
| metricsKit - Outbound Click | Click - Just Links | Wait for tags 2000ms (timeout 2000ms); validation wait off |
| metricsKit - Scroll Depth | Scroll Depth | Vertical thresholds 25 / 50 / 75 / 100 % |
| metricsKit - File Download | Link Click | Wait for tags 2000ms; validation wait off |
| metricsKit - Ecommerce Purchase | Custom Event | Event name purchase |
| metricsKit - Ecommerce Add To Cart | Custom Event | Event name add_to_cart |
| metricsKit - Ecommerce Begin Checkout | Custom Event | Event name begin_checkout |
| metricsKit - Ecommerce View Item | Custom Event | Event name view_item |
| metricsKit - Ecommerce Refund | Custom Event | Event name refund |
| Tag name | Event name | Event parameters | Firing trigger |
|---|---|---|---|
| metricsKit - GA4 Event - Form Submit | form_submit | formId, formClasses, formText, formUrl, formDestination | Form Submission |
| metricsKit - GA4 Event - Outbound Click | click | linkUrl, linkDomain, linkText, outbound=true | Outbound Click |
| metricsKit - GA4 Event - Scroll | scroll | percent_scrolled | Scroll Depth |
| metricsKit - GA4 Event - File Download | file_download | file_name, link_url, link_text | File Download |
| metricsKit - GA4 Event - Affiliate Click | affiliate_click | affiliate=true, affiliate_program, affiliate_id, affiliate_type, link_url, link_text | Outbound Click |
| metricsKit - GA4 Event - Ecommerce Purchase | purchase | value, currency, transaction_id, items (from the dataLayer ecommerce object) | Ecommerce Purchase |
| metricsKit - GA4 Event - Ecommerce Add To Cart | add_to_cart | value, currency, transaction_id, items | Ecommerce Add To Cart |
| metricsKit - GA4 Event - Ecommerce Begin Checkout | begin_checkout | value, currency, transaction_id, items | Ecommerce Begin Checkout |
| metricsKit - GA4 Event - Ecommerce View Item | view_item | value, currency, transaction_id, items | Ecommerce View Item |
| metricsKit - GA4 Event - Ecommerce Refund | refund | value, currency, transaction_id, items | Ecommerce Refund |
Import file structure example#
{
"containerVersion": {
"path": "accounts/0000000/containers/0000000/versions/0",
"name": "metricsKit Tracking (example.com)",
"trigger": [
{ "triggerId": "100", "name": "metricsKit - Form Submission",
"type": "FORM_SUBMISSION", "fingerprint": "" },
{ "triggerId": "101", "name": "metricsKit - Outbound Click",
"type": "LINK_CLICK", "fingerprint": "",
"waitForTags": { "type": "TEMPLATE", "value": "2000" },
"waitForTagsTimeout": { "type": "TEMPLATE", "value": "2000" },
"checkValidation": { "type": "BOOLEAN", "value": "false" } },
{ "triggerId": "104", "name": "metricsKit - Ecommerce Purchase",
"type": "CUSTOM_EVENT", "fingerprint": "",
"customEventFilter": [{ "type": "EQUALS", "parameter": [
{ "type": "TEMPLATE", "key": "arg0", "value": "{{_event}}" },
{ "type": "TEMPLATE", "key": "arg1", "value": "purchase" } ] }] }
],
"variable": [
{ "name": "mk - Affiliate Detector", "type": "jsm", "fingerprint": "",
"parameter": [{ "type": "TEMPLATE", "key": "javascript",
"value": "function(){ /* generated from your AFF patterns, per-site */ }" }] },
{ "name": "dlv - ecommerce.value", "type": "v", "fingerprint": "",
"parameter": [{ "type": "TEMPLATE", "key": "name", "value": "ecommerce.value" }] }
],
"tag": [
{ "tagId": "200", "name": "metricsKit - GA4 Event - Ecommerce Purchase",
"type": "gaawe", "fingerprint": "",
"parameter": [
{ "type": "TEMPLATE", "key": "measurementIdOverride", "value": "G-XXXXXXXXXX" },
{ "type": "TEMPLATE", "key": "eventName", "value": "purchase" },
{ "type": "TEMPLATE", "key": "value", "value": "{{dlv - ecommerce.value}}" },
{ "type": "TEMPLATE", "key": "items", "value": "{{dlv - ecommerce.items}}" } ],
"firingTriggerId": ["104"] }
]
}
}Event mapping quick reference#
Customer event → dataLayer / GA4 event name:
| Shopify customer event | dataLayer / GA4 event |
|---|---|
| payment_info_submitted | add_payment_info |
| checkout_address_info_submitted | add_shipping_info / view_item_list |
| product_viewed | view_item |
| product_added_to_cart | add_to_cart |
| product_removed_from_cart | remove_from_cart |
| cart_viewed | view_cart |
| collection_viewed | view_item_list |
| search_submitted | search |
| checkout_started | begin_checkout |
| checkout_completed | purchase |
| checkout_contact_info_submitted | login |
Verification#
- Shopify admin → Settings → Customer events; confirm the pixel is Connected
- Place a test order (Shopify Bogus Gateway or another test payment)
- The purchase event appears in GA4 Realtime after about 5-30 minutes
- Verify data in the metricsKit ecommerce attribution report after 24-48 hours
References#
- Shopify Customer events: help.shopify.com/en/manual/promoting-marketing/pixels/customer-events
- Google & YouTube app (official Shopify integration): help.shopify.com/en/manual/promoting-marketing/pixels/google
- Shopify Web Pixel extension: shopify.dev/docs/api/web-pixels-extension
- Google's note on why GTM is unsupported in the sandbox: support.google.com/tagmanager/answer/16000892