A guide for Shopify / Shopify Plus stores: how to get server-side tracking running via DataNostro without a custom Shopify app. We use the native Shopify Custom Pixel + GTM web/server containers, so you have full control over consent and the dataLayer.
Who this guide is for
- Shopify or Shopify Plus (you need the Customer Events section, available on all plans)
- Time: 75–120 minutes for the first setup
- For headless Shopify (Hydrogen, Storefront API) the process differs — see the bottom section
What you'll need
- Shopify admin access (Owner or Staff with the Customer Events permission)
- A Google Tag Manager account — a web + sGTM container
- DNS access for the tracking subdomain
- A DataNostro account and a created container
Step 1 — Create a Custom Pixel with a GTM dataLayer bridge
Since 2023 Shopify has a Customer Events framework — sandboxed JavaScript that emits structured events. Here we bridge it into the dataLayer that GTM understands.
- Shopify admin → Settings → Customer Events → Add custom pixel.
- Name it
DataNostro GTM Bridge, customer privacy = data sale opt-out off (handled by Consent Mode), permission = Not Required (let the cookie banner decide). - Paste this into the code editor:
// DataNostro GTM Bridge — emits dataLayer events from Shopify Customer Events
window.dataLayer = window.dataLayer || [];
analytics.subscribe("page_viewed", (event) => {
window.dataLayer.push({
event: "page_view",
page_location: event.context.window.location.href,
page_title: event.context.document.title,
});
});
analytics.subscribe("product_viewed", (event) => {
const v = event.data.productVariant;
window.dataLayer.push({
event: "view_item",
ecommerce: {
currency: v.price.currencyCode,
value: v.price.amount,
items: [{
item_id: v.product.id,
item_name: v.product.title,
item_variant: v.title,
price: v.price.amount,
quantity: 1,
}],
},
});
});
analytics.subscribe("product_added_to_cart", (event) => {
const line = event.data.cartLine;
window.dataLayer.push({
event: "add_to_cart",
ecommerce: {
currency: line.cost.totalAmount.currencyCode,
value: line.cost.totalAmount.amount,
items: [{
item_id: line.merchandise.product.id,
item_name: line.merchandise.product.title,
price: line.cost.totalAmount.amount,
quantity: line.quantity,
}],
},
});
});
analytics.subscribe("checkout_completed", (event) => {
const c = event.data.checkout;
window.dataLayer.push({
event: "purchase",
ecommerce: {
transaction_id: c.order.id,
value: c.totalPrice.amount,
currency: c.currencyCode,
tax: c.totalTax ? c.totalTax.amount : 0,
shipping: c.shippingLine ? c.shippingLine.price.amount : 0,
items: c.lineItems.map(l => ({
item_id: l.variant.product.id,
item_name: l.title,
item_variant: l.variant.title,
price: l.variant.price.amount,
quantity: l.quantity,
})),
},
});
});
Save → enable. This pixel will now automatically emit GA4-format events into window.dataLayer on all pages (including the checkout, which is on an isolated subdomain in Shopify).
Step 2 — Add the GTM container to Shopify
Shopify doesn't let you add a <script> tag to the theme easily — but via the Custom Pixel + a theme.liquid edit it works.
- Shopify admin → Online Store → Themes → … → Edit code.
- Open
theme.liquidand right after the opening<head>insert the standard GTM snippet:
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
Replace GTM-XXXXXX with your ID. Save.
⚠️ Watch out for the Shopify Checkout: the standard theme.liquid doesn't load on the checkout (it's an isolated subdomain). But the Custom Pixel from Step 1 does — which is why the purchase event is emitted correctly there too.
Step 3 — Deploy the sGTM container in DataNostro
Just like with other CMSs:
- In Google Tag Manager, create a new Server container.
- Admin → Export Container, download the JSON.
- DataNostro dashboard → Containers → Create new → upload the JSON.
- DataNostro returns the endpoint URL (e.g.
track.yourstore.cz) and DNS instructions.
Step 4 — DNS
At the DNS provider of your shop domain (not myshopify.com — Shopify won't let you change its DNS), add a CNAME:
Type: CNAME
Name: track
Target: containers.datanostro.com
TTL: 300 (for testing), then 3600
After propagation (5–60 min) DataNostro issues a Let's Encrypt SSL.
Step 5 — Switch Web GTM tags to server-side
In the GTM web container:
- Create a GA4 Configuration tag (if you don't have one yet).
- Fields to Set:
Field: server_container_url Value: https://track.yourstore.cz - Triggers:
page_view,view_item,add_to_cart,purchase(the event names from the Custom Pixel in Step 1). - Submit → Publish.
Step 6 — End-to-end test
- Shopify preview mode or a password-protected store + make a test purchase for CZK 1.
- In DataNostro Activity, watch the real-time events.
- In GA4 DebugView, verify the
purchaseevent with the correct value + items. - In Meta Events Manager Test Events, verify the Meta CAPI hit.
Headless Shopify (Hydrogen, Storefront API)
With a headless setup you don't use theme.liquid — you send dataLayer events manually from React/Vue components. Use analytics.publish or directly window.dataLayer.push in fetch hooks after committing the order. The Custom Pixel for the checkout works the same in Hydrogen, because the Customer Events API is shared.
Common problems
"The purchase event is missing — I see page_view but not the order"
The Custom Pixel loads in isolation on the checkout. If the checkout_completed event doesn't arrive, check the Customer Events log in the Shopify admin (Customer Events → Pixel detail → Errors). The most common cause: syntax in the JS code.
"Shopify blocks my 3rd-party domain"
Shopify has a Content Security Policy that limits where events can go. The Custom Pixel runs in a sandbox — it can call any domain via fetch. Web GTM (from theme.liquid) can too, as long as inline script is allowed (the default is yes).
"The Order ID in GA4 doesn't match the Shopify Order Number"
In the Custom Pixel use c.order.id (the internal ID). If you want the displayed Order Number (e.g. #1042), use c.order.name. Then transform it back to the transaction_id field in server-side GTM.
Next steps
- Power-Ups — Cookie Keeper, Click ID Restorer — more important for Shopify, where Safari ITP cuts the
_gacookie aggressively - Meta CAPI deduplication — Shopify often duplicates purchase events between the Pixel + CAPI
- Google Ads Enhanced Conversions — uses the email from the checkout for a better match