# Installing GTM on Shopify in 2026 — Complete Guide

## 1\. The Recommended Method: Shopify's Custom Pixels + Web Pixels API

Shopify has moved away from direct theme code injection for tracking. The **Customer Events** system (via **Web Pixels API**) is now the standard approach.

### Why this matters:

* Shopify's checkout is **sandboxed** — you can't inject arbitrary scripts into checkout pages on non-Plus stores
    
* Custom Pixels run in a **sandbox iframe**, which affects how GTM behaves
    
* Direct theme injection still works for non-checkout pages, but you'll have gaps in conversion tracking
    

---

## 2\. Setup — Two-Part Installation

### Part A: Theme Code (Head & Body snippets)

This covers **all pages except checkout**.

**Step 1:** Go to **Shopify Admin → Online Store → Themes → Edit Code**

**Step 2:** Open `theme.liquid`

**Step 3:** Paste this right after the opening `<head>` tag:

```html
<!-- 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-XXXXXXX');</script>
<!-- End Google Tag Manager -->
```

**Step 4:** Paste this right after the opening `<body>` tag:

```html
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
```

### Part B: Custom Pixel (for Checkout & Post-Purchase Tracking)

This is **critical** — without this, you lose conversion/purchase data.

**Step 1:** Go to **Shopify Admin → Settings → Customer events**

**Step 2:** Click **Add custom pixel**

**Step 3:** Name it `GTM Checkout Tracking`

**Step 4:** Paste this code:

```javascript
// GTM Custom Pixel for Shopify Checkout
const script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX';
script.async = true;
document.head.appendChild(script);

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  'gtm.start': new Date().getTime(),
  event: 'gtm.js'
});

// Subscribe to Shopify standard events
analytics.subscribe('page_viewed', (event) => {
  window.dataLayer.push({
    event: 'page_view',
    page_location: event.context.document.location.href,
    page_title: event.context.document.title
  });
});

analytics.subscribe('product_viewed', (event) => {
  window.dataLayer.push({
    event: 'view_item',
    ecommerce: {
      items: [{
        item_id: event.data.productVariant?.id,
        item_name: event.data.productVariant?.title,
        price: event.data.productVariant?.price?.amount,
        currency: event.data.productVariant?.price?.currencyCode
      }]
    }
  });
});

analytics.subscribe('cart_viewed', (event) => {
  window.dataLayer.push({
    event: 'view_cart',
    ecommerce: {
      items: event.data.cart?.lines?.map(line => ({
        item_id: line.merchandise?.id,
        item_name: line.merchandise?.title,
        price: line.merchandise?.price?.amount,
        quantity: line.quantity
      }))
    }
  });
});

analytics.subscribe('product_added_to_cart', (event) => {
  window.dataLayer.push({
    event: 'add_to_cart',
    ecommerce: {
      items: [{
        item_id: event.data.cartLine?.merchandise?.id,
        item_name: event.data.cartLine?.merchandise?.title,
        price: event.data.cartLine?.merchandise?.price?.amount,
        quantity: event.data.cartLine?.quantity
      }]
    }
  });
});

analytics.subscribe('checkout_started', (event) => {
  window.dataLayer.push({
    event: 'begin_checkout',
    ecommerce: {
      value: event.data.checkout?.totalPrice?.amount,
      currency: event.data.checkout?.currencyCode,
      items: event.data.checkout?.lineItems?.map(item => ({
        item_id: item.variant?.id,
        item_name: item.title,
        price: item.variant?.price?.amount,
        quantity: item.quantity
      }))
    }
  });
});

analytics.subscribe('payment_info_submitted', (event) => {
  window.dataLayer.push({
    event: 'add_payment_info',
    ecommerce: {
      value: event.data.checkout?.totalPrice?.amount,
      currency: event.data.checkout?.currencyCode
    }
  });
});

analytics.subscribe('checkout_completed', (event) => {
  window.dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: event.data.checkout?.order?.id,
      value: event.data.checkout?.totalPrice?.amount,
      tax: event.data.checkout?.totalTax?.amount,
      shipping: event.data.checkout?.shippingLine?.price?.amount,
      currency: event.data.checkout?.currencyCode,
      items: event.data.checkout?.lineItems?.map(item => ({
        item_id: item.variant?.id,
        item_name: item.title,
        price: item.variant?.price?.amount,
        quantity: item.quantity
      }))
    }
  });
});
```

**Step 5:** Set **Customer data sharing** to "Not required" (or adjust based on your consent needs)

**Step 6:** Click **Save** → then **Connect**

---

## 3\. Important Caveats

### Sandbox Limitations

Custom Pixels run in a **sandboxed iframe**, which means:

* **Cookies** set by GTM tags inside the pixel won't share with the main page
    
* **Facebook Pixel, Google Ads, GA4** — these work fine via GTM in the sandbox for event tracking
    
* **Session stitching** between browse → checkout can be tricky — use Shopify's built-in integrations for Facebook/Google where possible as a backup
    

### Consent Mode

For your clients in Taiwan/Asia, if GDPR-style compliance isn't required, this is simpler. But if needed:

* Implement **Google Consent Mode v2** in your GTM container
    
* Use the pixel's permission settings to control when it fires
    

### Server-Side GTM (Advanced)

For clients like ALUXE or JOYCOLORi where data accuracy is critical:

* Consider setting up **server-side GTM** (sGTM) as a proxy
    
* This bypasses ad blocker issues and improves data quality
    
* Runs on Google Cloud or a custom domain endpoint
    

---

## 4\. Testing Checklist

1. **GTM Preview Mode** — Use GTM's debug/preview to verify tags fire on browsing pages
    
2. **Place a test order** — Verify `purchase` event fires with correct `transaction_id`, `value`, and `items`
    
3. **GA4 DebugView** — Check real-time events in GA4
    
4. **Facebook Events Manager** — If running Meta Pixel via GTM, verify events in Test Events
    
5. **Check for duplicates** — Make sure you're not double-firing if Shopify's native Google/Meta integrations are also active
    

---

## 5\. Common Pitfalls

| Issue | Solution |
| --- | --- |
| Purchase event not firing | Make sure custom pixel is **Connected** (not just saved) |
| Duplicate transactions | Disable Shopify's native GA4 integration if using GTM for GA4 |
| GTM tags blocked by ad blockers | Consider server-side GTM |
| Cookie/session mismatch between site and checkout | Use Shopify's first-party integrations as backup for key conversions |
| dataLayer events not appearing in GTM Preview | Preview mode doesn't work inside the sandbox — test with real events |

---

## Quick Summary

For your Shopify clients, the **two-part approach** (theme.liquid + Custom Pixel) gives you the best coverage. For high-value e-commerce clients, layer in **server-side GTM** for maximum data accuracy. Always test with a real order before going live.

Need me to help configure specific GTM tags (GA4, Meta, Google Ads conversions) inside the container as well?
