Implement Google Tag Manager (GTM) on your PayloadCMS frontend to manage all marketing tags from a single interface and enable advanced tracking without code changes.
Prerequisites
- Google Tag Manager account created at tagmanager.google.com
- GTM Container ID (format:
GTM-XXXXXXX) - Access to your PayloadCMS frontend application code
- Understanding of React/Next.js
Method 1: Next.js Implementation (Recommended)
Step 1: Create GTM Configuration
File: lib/gtm.js
export const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;
export const initGTM = () => {
if (typeof window !== 'undefined' && GTM_ID) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'gtm.start': new Date().getTime(),
event: 'gtm.js',
});
}
};
export const pushToDataLayer = (data) => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push(data);
}
};
Step 2: Add to Application Layout
Pages Router (_app.js):
import { useEffect } from 'react';
import Script from 'next/script';
import * as gtm from '../lib/gtm';
function MyApp({ Component, pageProps }) {
useEffect(() => {
gtm.initGTM();
}, []);
return (
<>
{/* Google Tag Manager */}
<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','${gtm.GTM_ID}');
`,
}}
/>
<Component {...pageProps} />
</>
);
}
export default MyApp;
Add noscript to _document.js:
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;
return (
<Html lang="en">
<Head />
<body>
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
/>
</noscript>
<Main />
<NextScript />
</body>
</Html>
);
}
App Router (app/layout.js):
import Script from 'next/script';
export default function RootLayout({ children }) {
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;
return (
<html lang="en">
<head>
<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','${GTM_ID}');
`,
}}
/>
</head>
<body>
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
/>
</noscript>
{children}
</body>
</html>
);
}
Step 3: Add Environment Variable
File: .env.local
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
Method 2: React (CRA) Implementation
File: src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TagManager from 'react-gtm-module';
import App from './App';
const GTM_ID = process.env.REACT_APP_GTM_ID;
TagManager.initialize({
gtmId: GTM_ID,
});
ReactDOM.render(<App />, document.getElementById('root'));
Install package:
npm install react-gtm-module
Tracking Page Views
Pages Router
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { pushToDataLayer } from '../lib/gtm';
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
pushToDataLayer({
event: 'pageview',
page: url,
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return <Component {...pageProps} />;
}
App Router
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
import { pushToDataLayer } from '@/lib/gtm';
export default function PageViewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
pushToDataLayer({
event: 'pageview',
page: pathname + searchParams.toString(),
});
}, [pathname, searchParams]);
return null;
}
Custom Event Tracking
Track Form Submissions
import { pushToDataLayer } from '@/lib/gtm';
const ContactForm = () => {
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/contact-submissions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
}),
});
if (response.ok) {
pushToDataLayer({
event: 'form_submission',
formName: 'contact',
formId: 'contact_form',
});
}
} catch (error) {
pushToDataLayer({
event: 'form_error',
formName: 'contact',
errorMessage: error.message,
});
}
};
return (
<form
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">Submit</button>
</form>
);
};
Track Button Clicks
import { pushToDataLayer } from '@/lib/gtm';
const CTAButton = ({ label, destination }) => {
const handleClick = () => {
pushToDataLayer({
event: 'cta_click',
ctaLabel: label,
ctaDestination: destination,
});
};
return (
<button
{label}
</button>
);
};
Configuring GTM Container
Step 1: Create Page View Trigger
In GTM:
- Triggers > New
- Trigger Type: Custom Event
- Event Name:
pageview - Save
Step 2: Create GA4 Tag
- Tags > New
- Tag Type: Google Analytics: GA4 Event
- Measurement ID: Your GA4 ID
- Event Name:
page_view - Trigger: Pageview trigger created above
- Save
Step 3: Create Form Submission Tag
- Tags > New
- Tag Type: Google Analytics: GA4 Event
- Event Name:
form_submission - Trigger: Custom Event =
form_submission - Add Parameters:
form_name:\{\{DLV - formName\}\}form_id:\{\{DLV - formId\}\}
- Save
Data Layer Variables
Create Variables in GTM
DLV - formName:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
formName
DLV - formId:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
formId
DLV - page:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
page
Testing GTM
Use Preview Mode
- In GTM, click Preview
- Enter your site URL
- GTM Debug panel opens
- Verify:
- Container loads
- Tags fire on events
- Variables populate correctly
Check dataLayer
// In browser console
console.log(window.dataLayer);
Best Practices
- Initialize Early: Load GTM before other scripts
- Use Data Layer: Always push events to dataLayer, not direct tracking calls
- Consistent Naming: Use consistent event and variable names
- Document Events: Maintain documentation of custom events
- Test Thoroughly: Use Preview mode before publishing