Installing Google Tag Manager on Netlify CMS / Decap CMS | OpsBlu Docs

Installing Google Tag Manager on Netlify CMS / Decap CMS

Step-by-step guide to implementing GTM on static sites built with Hugo, Jekyll, Gatsby, Next.js, and 11ty

This guide provides detailed instructions for installing Google Tag Manager (GTM) on static sites built with Netlify CMS (now Decap CMS) across different static site generators.

Prerequisites

  1. Create GTM Account and Container

    • Go to tagmanager.google.com
    • Create account (if needed)
    • Create container (select "Web" as target platform)
    • Copy Container ID (format: GTM-XXXXXXX)
  2. Have Netlify CMS site using a static site generator

  3. Set up environment variables (recommended):

    • Production Container ID
    • Staging/Preview Container ID (optional)

GTM Container Code

When you create a GTM container, you'll receive two code snippets:

Head snippet (loads GTM):

<!-- 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 -->

Body snippet (noscript fallback):

<!-- 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) -->

Method 1: Hugo + GTM

Step 1: Configure Environment Variables

Add to netlify.toml:

[context.production.environment]
  HUGO_GTM_ID = "GTM-PROD-XXX"

[context.deploy-preview.environment]
  HUGO_GTM_ID = "GTM-DEV-XXX"

[context.branch-deploy.environment]
  HUGO_GTM_ID = "GTM-DEV-XXX"

Step 2: Create GTM Head Partial

Create layouts/partials/gtm-head.html:

{{ with getenv "HUGO_GTM_ID" }}
<!-- 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','{{ . }}');</script>
<!-- End Google Tag Manager -->
{{ end }}

Step 3: Create GTM Body Partial

Create layouts/partials/gtm-body.html:

{{ with getenv "HUGO_GTM_ID" }}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ . }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{{ end }}

Step 4: Update Base Template

Modify layouts/_default/baseof.html:

<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
  <head>
    <meta charset="UTF-8">
    <title>{{ .Title }}</title>
    {{ partial "gtm-head.html" . }}
  </head>
  <body>
    {{ partial "gtm-body.html" . }}
    {{ block "main" . }}{{ end }}
  </body>
</html>

Step 5: Deploy and Verify

git add .
git commit -m "Add Google Tag Manager"
git push origin main

Method 2: Jekyll + GTM

Step 1: Configure Container ID

Add to _config.yml:

google_tag_manager: GTM-XXXXXXX

# Or environment-specific
production:
  google_tag_manager: GTM-PROD-XXX

development:
  google_tag_manager: GTM-DEV-XXX

Step 2: Create GTM Head Include

Create _includes/gtm-head.html:

{% if jekyll.environment == "production" %}
  {% if site.google_tag_manager %}
<!-- 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','{{ site.google_tag_manager }}');</script>
<!-- End Google Tag Manager -->
  {% endif %}
{% endif %}

Step 3: Create GTM Body Include

Create _includes/gtm-body.html:

{% if jekyll.environment == "production" %}
  {% if site.google_tag_manager %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ site.google_tag_manager }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
  {% endif %}
{% endif %}

Step 4: Update Default Layout

Modify _layouts/default.html:

<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
  <head>
    <meta charset="UTF-8">
    <title>{{ page.title | default: site.title }}</title>
    {% include gtm-head.html %}
  </head>
  <body>
    {% include gtm-body.html %}
    {{ content }}
  </body>
</html>

Step 5: Configure Netlify Environment

In netlify.toml:

[build]
  command = "JEKYLL_ENV=production bundle exec jekyll build"
  publish = "_site"

[context.production.environment]
  JEKYLL_ENV = "production"

[context.deploy-preview.environment]
  JEKYLL_ENV = "development"

Method 3: Gatsby + GTM

Step 1: Install Plugin

npm install gatsby-plugin-google-tagmanager

Step 2: Configure Plugin

Add to gatsby-config.js:

module.exports = {
  plugins: [
    {
      resolve: "gatsby-plugin-google-tagmanager",
      options: {
        id: process.env.GATSBY_GTM_ID || "GTM-XXXXXXX",

        // Include GTM in development (optional, usually false)
        includeInDevelopment: false,

        // datalayer to be set before GTM is loaded
        defaultDataLayer: { platform: "gatsby" },

        // Specify optional GTM environment details
        gtmAuth: process.env.GATSBY_GTM_AUTH,
        gtmPreview: process.env.GATSBY_GTM_PREVIEW,
        dataLayerName: "dataLayer",

        // Name of the event that is triggered on every Gatsby route change
        routeChangeEventName: "gatsby-route-change",
      },
    },
  ],
};

Step 3: Set Environment Variables

Create .env.production:

GATSBY_GTM_ID=GTM-PROD-XXX

Create .env.development:

GATSBY_GTM_ID=GTM-DEV-XXX

Add to Netlify UI:

Site Settings → Build & Deploy → Environment

GATSBY_GTM_ID = GTM-PROD-XXX

Step 4: Configure Route Change Tracking

The plugin automatically tracks route changes. To customize:

Create gatsby-browser.js:

export const location, prevLocation }) => {
  if (typeof window !== 'undefined' && window.dataLayer && prevLocation) {
    window.dataLayer.push({
      event: 'gatsby-route-change',
      page: location.pathname,
    });
  }
};

Step 5: Build and Deploy

gatsby build
git add .
git commit -m "Add Google Tag Manager"
git push origin main

Method 4: Next.js + GTM

Step 1: Create GTM Component

Create components/GoogleTagManager.js:

import Script from 'next/script';

const GoogleTagManager = ({ gtmId }) => {
  if (!gtmId) return null;

  return (
    <>
      {/* GTM Script */}
      <Script
        id="gtm-script"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            (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','${gtmId}');
          `,
        }}
      />
    </>
  );
};

export default GoogleTagManager;

Step 2: Add to _app.js

Modify pages/_app.js:

import GoogleTagManager from '../components/GoogleTagManager';

function MyApp({ Component, pageProps }) {
  const gtmId = process.env.NEXT_PUBLIC_GTM_ID;

  return (
    <>
      {process.env.NODE_ENV === 'production' && <GoogleTagManager gtmId={gtmId} />}
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

Step 3: Add Noscript to _document.js

Create or modify pages/_document.js:

import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  const gtmId = process.env.NEXT_PUBLIC_GTM_ID;

  return (
    <Html lang="en">
      <Head />
      <body>
        {/* GTM noscript */}
        {gtmId && (
          <noscript>
            <iframe
              src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
              height="0"
              width="0"
              style={{ display: 'none', visibility: 'hidden' }}
            />
          </noscript>
        )}
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Step 4: Configure Environment Variables

Create .env.local:

NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX

Add to netlify.toml:

[context.production.environment]
  NEXT_PUBLIC_GTM_ID = "GTM-PROD-XXX"

[context.deploy-preview.environment]
  NEXT_PUBLIC_GTM_ID = "GTM-DEV-XXX"

Step 5: Track Page Views

Create lib/gtm.js:

export const pageview = (url) => {
  if (typeof window !== 'undefined' && window.dataLayer) {
    window.dataLayer.push({
      event: 'pageview',
      page: url,
    });
  }
};

Update pages/_app.js:

import { useRouter } from 'next/router';
import { useEffect } from 'react';
import * as gtm from '../lib/gtm';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    router.events.on('routeChangeComplete', gtm.pageview);
    return () => {
      router.events.off('routeChangeComplete', gtm.pageview);
    };
  }, [router.events]);

  // ... rest of component
}

Method 5: 11ty (Eleventy) + GTM

Step 1: Create Data File

Create _data/gtm.js:

module.exports = {
  id: process.env.GTM_ID || "GTM-XXXXXXX"
};

Step 2: Create GTM Partials

Create _includes/gtm-head.njk:

{% if gtm.id %}
<!-- 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.id }}');</script>
<!-- End Google Tag Manager -->
{% endif %}

Create _includes/gtm-body.njk:

{% if gtm.id %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ gtm.id }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endif %}

Step 3: Update Base Layout

Modify _includes/layouts/base.njk:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    {% include "gtm-head.njk" %}
  </head>
  <body>
    {% include "gtm-body.njk" %}
    {{ content | safe }}
  </body>
</html>

Step 4: Configure Environment

Add to netlify.toml:

[build]
  command = "npm run build"
  publish = "_site"

[build.environment]
  GTM_ID = "GTM-PROD-XXX"

[context.deploy-preview.environment]
  GTM_ID = "GTM-DEV-XXX"

Verification Steps

1. Install Tag Assistant

Install Tag Assistant Chrome Extension

2. Check GTM Container Loads

  1. Visit your deployed site
  2. Open browser DevTools → Console
  3. Type: console.log(google_tag_manager)
  4. Should see GTM object with your container ID

3. Use GTM Preview Mode

  1. Open GTM container
  2. Click Preview button
  3. Enter your site URL
  4. GTM debugger panel opens
  5. Verify "Container Loaded" event fires

4. Check Data Layer

// In browser console
console.log(window.dataLayer);
// Should see array with GTM initialization

5. Network Tab Verification

  1. Open DevTools → Network tab
  2. Filter by "gtm.js"
  3. Reload page
  4. Verify request to googletagmanager.com/gtm.js?id=GTM-XXXXXXX

Common Setup Issues

GTM Not Loading on Localhost

Issue: GTM doesn't load during local development.

Solution: This is intentional (configured via includeInDevelopment: false).

For testing locally:

  • Temporarily set to true
  • Or use GTM preview mode pointing to localhost
  • Or test on preview deploy URLs

Environment Variables Not Working

Issue: Container ID is undefined or shows placeholder.

Solution:

  • Gatsby: Prefix with GATSBY_
  • Next.js: Prefix with NEXT_PUBLIC_
  • Hugo/Jekyll: Set in Netlify build environment
  • Verify in Netlify UI: Site Settings → Build & Deploy → Environment

Preview Deploys Using Production Container

Issue: Preview URLs load production GTM container.

Solution: Use context-specific environment variables:

[context.production.environment]
  GTM_ID = "GTM-PROD-XXX"

[context.deploy-preview.environment]
  GTM_ID = "GTM-DEV-XXX"

Noscript Fallback Not Working

Issue: Image pixel doesn't load for no-JS users.

Solution:

  • Verify noscript tag is immediately after opening <body> tag
  • Check iframe URL includes correct GTM ID
  • Ensure no CSS hiding iframe

Next Steps