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
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)
Have Netlify CMS site using a static site generator
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
- Visit your deployed site
- Open browser DevTools → Console
- Type:
console.log(google_tag_manager) - Should see GTM object with your container ID
3. Use GTM Preview Mode
- Open GTM container
- Click Preview button
- Enter your site URL
- GTM debugger panel opens
- 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
- Open DevTools → Network tab
- Filter by "gtm.js"
- Reload page
- 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
- Configure Data Layer - Add content metadata
- Set Up GA4 in GTM - Add analytics tags
- Debug Tracking Issues - Troubleshoot problems
Related Resources
- GTM Fundamentals - Universal GTM concepts
- GTM Overview - Why use GTM for static sites
- Performance Impact - Optimize GTM loading