Google Tag Manager Setup on PayloadCMS | OpsBlu Docs

Google Tag Manager Setup on PayloadCMS

Implement Google Tag Manager on PayloadCMS frontends for centralized tag management and advanced tracking capabilities.

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

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:

  1. Triggers > New
  2. Trigger Type: Custom Event
  3. Event Name: pageview
  4. Save

Step 2: Create GA4 Tag

  1. Tags > New
  2. Tag Type: Google Analytics: GA4 Event
  3. Measurement ID: Your GA4 ID
  4. Event Name: page_view
  5. Trigger: Pageview trigger created above
  6. Save

Step 3: Create Form Submission Tag

  1. Tags > New
  2. Tag Type: Google Analytics: GA4 Event
  3. Event Name: form_submission
  4. Trigger: Custom Event = form_submission
  5. Add Parameters:
    • form_name: \{\{DLV - formName\}\}
    • form_id: \{\{DLV - formId\}\}
  6. 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

  1. In GTM, click Preview
  2. Enter your site URL
  3. GTM Debug panel opens
  4. Verify:
    • Container loads
    • Tags fire on events
    • Variables populate correctly

Check dataLayer

// In browser console
console.log(window.dataLayer);

Best Practices

  1. Initialize Early: Load GTM before other scripts
  2. Use Data Layer: Always push events to dataLayer, not direct tracking calls
  3. Consistent Naming: Use consistent event and variable names
  4. Document Events: Maintain documentation of custom events
  5. Test Thoroughly: Use Preview mode before publishing

Next Steps


Additional Resources