Skip to main content
Code auditCheck every pull request for privacy riskWebsite auditCatch scripts and vendors that appear in productionInth AgentAsk what changed and follow the answer to its sourceCookie consentFast consent that lives in your codebase
AboutHandbookBlogOSS
AI feature reviewsFundraising Due DiligencePrivacy impact reviewsEnterprise customer reviewsCookie and tracking audits
Pricing
Sign inRun a free scan
Run a free scan
Back to the blog

Published September 7, 2026

How to add Google Tag Manager to Next.js with c15t

KW

Kaylee Williams

Founding Engineer

Add Google Tag Manager to a Next.js App Router project with c15t, then use Consent Mode v2 so GTM-managed tags wait for the right consent state.

Topic
Guides
Reading time
8 min read

Google Tag Manager is easy to add to a page and hard to configure correctly. A Next.js app can load the GTM container in one place, but the tags inside that container still need consent signals before they fire analytics, ads, or conversion code.

c15t handles the browser-side consent state and loads the GTM container with Google Consent Mode v2 defaults set to denied. GTM can then re-evaluate its tags when the visitor accepts, rejects, or changes consent.

This guide gives developer implementation information and general legal context. It is not legal advice. Ask counsel to review your consent text, tag categories, regional rules, and vendor list before release.

What you will build

By the end, you can:

  • Add c15t to a Next.js App Router project.
  • Register Google Tag Manager through @c15t/scripts/google-tag-manager.
  • Enable Consent Overview in GTM.
  • Create a consent-update trigger for tag re-evaluation.
  • Push application events to window.dataLayer without adding extra consent guards in React.
  • Verify the setup in GTM Preview and Google Tag Assistant.

This guide uses a hosted c15t backend and the prebuilt c15t consent UI. If your app already has c15t installed, start at the GTM registration step.

How c15t and GTM split the work

Most c15t script integrations wait until the matching consent category is granted before the vendor script enters the DOM. GTM is different.

The c15t GTM integration loads the container on page start because GTM has its own consent system. c15t sets Consent Mode v2 defaults to denied, then pushes updates when the visitor changes consent. In the c15t docs, the GTM integration is categorized as necessary, uses alwaysLoad, and sends a default consent-update event on consent change.

That means your app should not add the normal GTM snippet with next/script. It should not add a second GTM container in app/layout.tsx either. Let c15t own the GTM script so the default denied state and later updates happen in the expected order.

Google’s Consent Mode guide says a site needs a default consent state before tags send measurement data, then an update after the user interacts with consent controls. Google also notes that code order matters because late defaults may not work as intended.

Install c15t and the script helpers

Install the Next.js package and the built-in script integrations package.

npm install @c15t/nextjs @c15t/scripts
pnpm add @c15t/nextjs @c15t/scripts
yarn add @c15t/nextjs @c15t/scripts
bun add @c15t/nextjs @c15t/scripts

Import the c15t stylesheet from your app-level CSS file if you use the prebuilt banner and dialog.

/* app/globals.css */
@import "@c15t/nextjs/styles.css";

You will also need two public setup values:

  • Your GTM container ID, such as GTM-XXXXXXX.
  • Your hosted c15t backend URL, such as https://your-instance.c15t.dev.

These values are not secrets. The browser can see the GTM container ID and the c15t backend URL once the site loads, so this guide keeps them directly in the example provider.

Register Google Tag Manager in the c15t provider

Create a client provider and pass the GTM helper through the scripts option.

// components/consent-manager/provider.tsx
'use client';

import { type ReactNode } from 'react';
import {
  ConsentBanner,
  ConsentDialog,
  ConsentManagerProvider,
} from '@c15t/nextjs';
import { googleTagManager } from '@c15t/scripts/google-tag-manager';

export function ConsentProvider({ children }: { children: ReactNode }) {
  return (
    <ConsentManagerProvider
      options={{
        mode: 'hosted',
        backendURL: 'https://your-instance.c15t.dev',
        consentCategories: ['necessary', 'measurement', 'marketing'],
        scripts: [
          googleTagManager({
            id: 'GTM-XXXXXXX',
          }),
        ],
      }}
    >
      <ConsentBanner />
      <ConsentDialog />
      {children}
    </ConsentManagerProvider>
  );
}

Replace GTM-XXXXXXX and https://your-instance.c15t.dev with your real values. If your staging and production sites use different GTM containers or c15t backends, choose the value in your app configuration. You do not need to treat either value as a secret.

Mount the provider at the app root.

// app/layout.tsx
import { ConsentProvider } from '@/components/consent-manager/provider';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ConsentProvider>{children}</ConsentProvider>
      </body>
    </html>
  );
}

Start the app and confirm the consent banner appears. Then open the browser Network panel and check for one GTM container request. If you see another GTM request from next/script, a CMS plugin, or a hardcoded snippet, remove the duplicate before you test consent behavior.

Enable Consent Overview in GTM

GTM can load with consent signals, but each tag still needs review. Google’s GTM consent documentation describes built-in consent checks, additional consent checks, and the Consent Overview screen. Use that screen to find tags that have not been reviewed.

In Google Tag Manager:

  1. Open your container.
  2. Go to Admin > Container Settings.
  3. Under Additional Settings, select Enable consent overview.
  4. Save the container settings.

Screenshot to add from the c15t docs before publication: Enable consent overview in Google Tag Manager.

After you enable it, open Tags and use the Consent Overview icon to inspect which tags are configured. For tags that need consent, set the matching additional consent checks. For example, a GA4 analytics tag commonly checks analytics_storage; an ads conversion tag may need ad-related consent types such as ad_storage, ad_user_data, or ad_personalization depending on your policy and Google product setup.

Do not assume the container is safe because the GTM script loads through c15t. The container is the shell. The tags inside it still need correct triggers and consent checks.

Create a custom consent-update trigger

c15t pushes a consent update event when the visitor changes consent. The c15t GTM docs use consent-update as the default event name.

Create a GTM trigger for that event:

  1. In GTM, open Triggers.
  2. Click New.
  3. Choose Custom Event.
  4. Set Event name to consent-update.
  5. Save the trigger.

Screenshot to add from the c15t docs before publication: Create a consent-update custom event trigger in Google Tag Manager.

Use this trigger on tags that should re-check consent after the visitor changes a setting. If a tag only fires on the original page view, it may miss the moment where consent changes from denied to granted.

Add the trigger to your GTM tags

Open each measurement or marketing tag that should run after consent is granted. Add the consent-update trigger alongside the normal trigger for that tag.

Screenshot to add from the c15t docs before publication: Add the consent-update trigger to a GTM tag.

Then review Advanced Settings > Consent Settings for the tag. Google tags may have built-in consent checks. You can also require additional consent checks so the tag only fires when the required consent types are granted.

A common starting point is:

Article data table
Tag typeTriggerConsent review
GA4 page view or eventPage view plus consent-updateCheck analytics consent behavior, including analytics_storage.
Google Ads conversionConversion trigger plus consent-update when relevantReview ad-related consent types with your legal and ads teams.
Non-Google marketing pixelIts normal event trigger plus consent-update if it should fire after acceptanceAdd required consent checks if the tag does not have built-in checks.
Strictly necessary operational tagIts operational triggerMark as no additional consent required only after review.

The exact mapping depends on your site, regions, vendor contracts, and policy. Treat the table as an implementation prompt, not a legal classification.

Push application events to the data layer

Because the c15t GTM integration uses alwaysLoad, the GTM container and window.dataLayer are available from page start. c15t sets Consent Mode v2 defaults to denied before the visitor chooses, and GTM-managed tags decide whether they can fire.

That means you can push app events without wrapping every dataLayer.push() in useConsentManager().has(...).

// lib/gtm.ts
declare global {
  interface Window {
    dataLayer?: Array<Record<string, unknown>>;
  }
}

export function pushGtmEvent(event: string, payload: Record<string, unknown> = {}) {
  if (typeof window === 'undefined') {
    return;
  }

  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event,
    ...payload,
  });
}

Use that helper from client components.

// components/signup-button.tsx
'use client';

import { pushGtmEvent } from '@/lib/gtm';

export function SignupButton() {
  return (
    <button
      type="button"
      onClick={() => {
        pushGtmEvent('signup', {
          plan: 'team',
          source: 'pricing-page',
        });
      }}
    >
      Start trial
    </button>
  );
}

This is different from integrations like Meta Pixel or some analytics SDKs, where the vendor global may not exist until consent is granted. For those integrations, guard calls with c15t consent state. We cover that pattern in How to integrate Meta Pixel in Next.js with c15t and How to add PostHog to Next.js with GDPR-aware consent.

Verify the setup

Use GTM Preview first because it shows the container event timeline and tag decisions.

  1. Open GTM Preview mode.
  2. Load your local or staging site.
  3. Confirm the GTM container loads on page load.
  4. Before consent, confirm non-essential tags do not fire.
  5. Accept consent in the c15t banner or dialog.
  6. Confirm a consent-update event appears in the GTM timeline.
  7. Confirm tags with matching consent checks fire after the update.
  8. Revoke consent, then confirm another update appears and affected tags stop firing.

Then use Google Tag Assistant to inspect Consent Mode. Google’s debugging guide says to check that the default consent state is set before tags fire, that updates happen after the visitor chooses, and that tags check the expected consent types.

For a Google-focused setup, check at least these Consent Mode v2 fields:

  • ad_storage
  • ad_user_data
  • ad_personalization
  • analytics_storage

If Tag Assistant reports that default consent was set too late, check for another GTM snippet or Google tag that runs outside c15t. Also check whether a tag in GTM is trying to set consent from a custom HTML tag. Google recommends Consent Initialization for consent-setting tags inside GTM and warns that some commands are not guaranteed to be available before the next trigger fires.

Common setup mistakes

The most common mistake is loading GTM twice. One copy may be managed by c15t and another may come from next/script, a marketing plugin, or a tag added in a layout file. Remove the unmanaged copy.

The second mistake is treating Consent Mode as a replacement for tag review. Consent Mode gives GTM consent state. It does not decide which of your tags are analytics, marketing, functional, or necessary.

The third mistake is pushing events only at page load. If a visitor grants consent after the page view, a tag that only listens to the initial page-view event may not fire. Add the consent-update trigger where you need tags to re-evaluate after consent changes.

The fourth mistake is testing only the accept path. Test reject, accept, customize, reload, and revoke. If your app has region-specific behavior, test those regions too.

Sources and further reading

  • c15t Google Tag Manager integration
  • c15t Next.js quickstart
  • c15t Script Loader
  • Google: Set up consent mode on websites
  • Google: Tag Manager consent mode support
  • Google: Troubleshoot consent mode with Tag Assistant
  • ICO: Cookies and similar technologies
  • EDPB: Guidelines 05/2020 on consent under Regulation 2016/679

Next steps

You now have a Next.js setup where c15t loads GTM, sends denied-by-default Consent Mode v2 state, and pushes updates when visitors change their consent choices.

You can now:

  • Keep GTM out of next/script and register it through c15t.
  • Configure Consent Overview and tag-level consent checks in GTM.
  • Add a consent-update trigger so tags re-check consent after the banner interaction.
  • Push app events to window.dataLayer without duplicating consent checks in every component.

Before publishing your GTM container, ask your legal or privacy reviewer to confirm the consent categories, vendor descriptions, regional defaults, and privacy notice updates.

Newer article · 02How privacy governance shows up in startup due diligencePrivacy governance often becomes visible during M&A, fundraising, and enterprise sales. This guide explains how Inth helps startups prepare consent, tracking, and product evidence before diligence starts.Guides/14 min readOlder article · 04How to test consent gates in a Next.js app with PlaywrightUse Playwright to test that consent-controlled scripts, requests, cookies, and storage stay blocked before consent, load after consent, and stop after withdrawal in a Next.js app.Guides/8 min read

Related

How To Integrate Meta Pixel Nextjs C15tHow To Add Posthog To Next Js With Gdpr Aware Consentc15t Google Tag Manager integrationc15t Next.js quickstartc15t Script LoaderRead the c15t Google Tag Manager docs

Notes from building privacy into the product

Notes from building Inth. Sent occasionally.

Inth connects what your company promised to what engineers just shipped.

Platform

  • Code audit
  • Website audit
  • Inth Agent
  • Consent banner
  • Pricing

Use Cases

  • Privacy Impact Review
  • AI Feature Reviews

Company

  • About
  • Blog
  • Open source
  • Contact

Resources

  • Documentation
  • GitHub
  • Cookiebench
  • Status

© 2026 Inth. All rights reserved.

  • Contact us
  • Privacy
  • Cookies
  • Terms