Skip to main content

Analytics

Google Tag

Configure the Google tag

Copy the tag ID, for example G-XXXXXXXXXX for Google Analytics or AW-XXXXXXXXX for Google Ads. Remove any gtag.js snippet already in your HTML and any tag-manager entry that loads the same tag.

npm install @c15t/integrations@alpha
src/consent-scripts.ts
import { gtag } from '@c15t/integrations/google-tag';

export const scripts = [gtag({ id: 'G-XXXXXXXXXX', category: 'measurement' })];

Register the scripts

Complete your framework quickstart first. Keep its Inth endpoint, policy, styles and consent UI. Remove the vendor's original script, SDK initializer or tag-manager entry, so the vendor loads only through c15t.

The vendor pages put the helper in src/consent-scripts.ts. If your framework quickstart already has a scripts array, such as the one in c15t.config.ts in the Next.js guide, add the helper to that array instead of creating a second file. The scripts export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration point for your framework below. These are edits to that provider, not a second provider.

Add the configuration to scripts in c15t.config.ts, next to next.config.ts:

import { defineConsentConfig } from 'c15t/next';
import { scripts } from './src/consent-scripts';

export default defineConsentConfig({ scripts });

Keep the rest of your config, such as mode and routePrefix, in the same call. ConsentRoot reads the config in the browser, so the layout keeps passing only state. App Router, Pages Router and static export all read the same file. See Next.js scripts and embeds.

Options

OptionDefaultBehavior
idRequiredTag ID passed to gtag('config', ...) and the loader URL. The helper trims it. Empty or whitespace-only values log an error and the script does not load.
categoryRequiredmeasurement for Analytics, marketing for Ads and Floodlight. With loadMode: 'after-consent', gtag/js waits for it. With 'always', it sets the script's permission, which callbacks receive, but does not delay loading.
loadMode'always'When gtag/js loads. See choose when Google loads.
configNoneParameters passed as the third argument to gtag('config', id, config).
consentMappingThe table belowReplaces the category-to-Google mapping.

The deprecated script option overrides fields of the returned script. Use the options above instead.

Choose when Google loads

loadModeUntil the category is allowedAfter the category is allowed
'always'Loads gtag/js and sends consent default with the current permissions, then config.Sends consent update with the mapped types granted.
'after-consent'Sends nothing to Google. The helper creates no dataLayer or gtag function.Loads gtag/js once. consent default carries the current permissions and comes before config. Later changes send update.

Use 'after-consent' when your policy forbids any request to Google before the visitor opts in:

src/consent-scripts.ts (partial)
gtag({ id: 'G-XXXXXXXXXX', category: 'measurement', loadMode: 'after-consent' }),

'after-consent' waits for the category to be allowed, not for a recorded choice. Under an opt-in policy, that happens when the visitor allows it. Under an opt-out or none policy, optional categories are allowed before a choice, so gtag/js loads on the first page unless a saved refusal or a privacy signal restricts the category; see policies. necessary is always allowed, so category: 'necessary' loads gtag/js before a choice in either mode. Use measurement or marketing.

This gives up part of Consent Mode. With 'always', Google tags send cookieless pings while a type is denied, and Google uses them to model conversions and behavior for visitors who refused or have not chosen. With 'after-consent', visitors whose category is denied send nothing, so Google has no data to model them from. Reports cover only visitors who allowed the category.

When the visitor withdraws the category, c15t reloads the page and the new page does not load gtag/js. With reloadOnConsentRevoked: false, the tag already running stays on the page and gets an update that denies the withdrawn types. A later grant reuses it instead of loading a second copy.

Google loads before a choice by default

With the default loadMode: 'always', the googleTagManager and gtag helpers set alwaysLoad: true. Before the visitor chooses, the helper creates the dataLayer queue, sends gtag('consent', 'default', ...) with the current permissions and loads Google's script. After each permission change it sends gtag('consent', 'update', ...). Google's tags then adjust what they store and send; see Google's Consent Mode overview.

So the browser contacts Google before consent. If your policy requires no Google request until the visitor allows it, set loadMode: 'after-consent'. The helper then creates nothing and requests nothing until its category is allowed. When it loads, it sends the default command first, with the permissions at that moment, and update commands after later changes. Under an opt-out or none policy, optional categories are allowed before a choice, so the helper loads on the first page.

c15t categoryGoogle consent types
necessarysecurity_storage
functionalityfunctionality_storage
measurementanalytics_storage
marketingad_storage, ad_user_data, ad_personalization
experiencepersonalization_storage

Each Google type is granted when its category is allowed and denied otherwise. If a visitor turns off the helper's vendor, every optional type is sent as denied. The consentMapping option replaces the whole table, so include every category you still want signalled.

Under an opt-out policy, the default command can grant types before the visitor has chosen anything. That is a permission, not a recorded choice.

Verify the Google tag

On a plain HTML page with the script tag, you gate Google's snippet instead and it loads only after consent; see HTML scripts.

With the default loadMode: 'always':

  1. In a private window with an opt-in policy, load the page. gtag/js loads. In Google Tag Assistant, the consent default command comes before config, with analytics_storage and ad_storage set to denied.
  2. Open Privacy settings and allow the tag's category. Tag Assistant shows an update command that grants the mapped types.
  3. Turn the category off again and save. c15t reloads the page, and the new page starts with those types denied.

With loadMode: 'after-consent':

  1. In a private window with an opt-in policy, filter DevTools Network by google and load the page. No request appears, and window.dataLayer is undefined in the Console.
  2. Allow the tag's category. gtag/js loads once. In Tag Assistant, the first command is consent default with the mapped types granted, before config.
  3. Turn the category off again and save. c15t reloads the page, and the new page makes no request to Google.

In either mode, with client-side navigation, check that each route change sends one page view. A separate router integration that also sends page_view doubles the count.

See the consent verification guide for hosting checks.