How to Integrate Google Tag Manager with Your Headless CMS
Connect Google Tag Manager to structured content so every published page can ship accurate dataLayer values, tags, consent logic, and analytics events without hand-editing templates.
What is Google Tag Manager?
Google Tag Manager is Google's tag orchestration tool for adding and updating marketing, analytics, advertising, and consent tags through containers. Web and data teams use it to control tags for Google Analytics 4, Google Ads, Floodlight, Meta Pixel, and custom HTML without redeploying the site for every tracking change. It's widely used by teams already running Google Analytics, Google Ads, or server-side tagging through Google's ecosystem.
Why integrate Google Tag Manager with a headless CMS?
Tracking usually breaks in small, boring ways. A campaign page ships without a page category, an author bio page misses its content group, or a new product template forgets to push ecommerce metadata into dataLayer. Google Tag Manager can handle the tags, triggers, and variables, but it still needs accurate page data at the right time.
Architecture overview
A typical Sanity and Google Tag Manager integration has two paths. First, your frontend reads structured content from Sanity's Content Lake and renders the page. At the same time, it pushes content metadata into window.dataLayer, such as content_type, author, section, campaign_id, and locale. GTM reads those values through Data Layer Variables and fires GA4, Google Ads, Floodlight, or custom tags based on your triggers. Second, Sanity can trigger server-side updates when content metadata changes. A Sanity webhook fires on publish, or a Sanity Function runs after the mutation. The Function uses GROQ to fetch the exact fields needed, then calls the Google Tag Manager API v2 with the googleapis SDK. Common API operations include listing variables in a workspace, creating or updating a Constant variable, creating a container version, and publishing it. For most teams, publishing to a staging workspace first is safer than automatically publishing every production container change.
Common use cases
Content grouping for GA4
Push page type, topic, author, language, and market from Sanity into dataLayer so GTM can send consistent content_group values to Google Analytics 4.
Campaign page tracking
Use Sanity fields like campaignId, funnelStage, and productLine to control GTM triggers for conversion tags, remarketing pixels, and event parameters.
Experiment and variant metadata
Expose experiment IDs, variant names, and audience labels from Sanity so GTM can send the same values to GA4, Ads, and testing tools.
Consent-aware tagging by region
Model region, language, and consent category fields in Sanity, then let GTM apply different tag firing rules for markets like the US, EU, and UK.
Step-by-step integration
- 1
Set up Google Tag Manager
Create a GTM account, container, and workspace. Note the account ID, container ID, workspace ID, and web container ID, such as GTM-ABC123. Enable the Google Tag Manager API in Google Cloud, create an OAuth client or service account for server-to-server calls, and grant that principal access in GTM user permissions. For container edits and publishing, use scopes like tagmanager.edit.containers and tagmanager.publish.
- 2
Add the GTM container to your frontend
Install the GTM snippet in your Next.js, Nuxt, Remix, Astro, or SvelteKit app. Add the script in the document head and the noscript iframe after the opening body tag. Then create a small dataLayer helper so each page can push content metadata before GTM tags fire.
- 3
Model tracking fields in Sanity Studio
Add schema fields that analytics teams actually use, such as contentType, section, topic, author, market, language, campaignId, paywallStatus, and experimentVariant. Keep them typed. A string list for section or market is easier to trust than freeform text copied into five templates.
- 4
Create the sync trigger
Use a Sanity webhook filtered to published documents that affect tracking, such as pages, posts, products, campaigns, and experiment pages. If you want to keep the logic inside Sanity, use Functions to run server-side code on content mutation events without running a separate worker.
- 5
Call the Google Tag Manager API
In your webhook handler or Function, fetch the published document from the Content Lake with GROQ. Then use the googleapis SDK to create or update GTM variables, triggers, or tags in the right workspace. For safety, many teams create a container version but publish manually after QA.
- 6
Test the frontend and tags
Use GTM Preview mode and Tag Assistant to confirm that dataLayer contains the expected values before tags fire. Then verify the downstream events in GA4 DebugView or your analytics destination. Test at least one new publish, one metadata update, one locale page, and one unpublished or draft state.
Code example
A minimal Next.js webhook handler that receives a Sanity publish event, fetches tracking fields with GROQ, and creates or updates a Google Tag Manager Constant variable in a workspace.
import {NextRequest, NextResponse} from 'next/server'
import {createClient} from '@sanity/client'
import {google} from 'googleapis'
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_READ_TOKEN!,
useCdn: false
})
export async function POST(req: NextRequest) {
const {_id} = await req.json()
const page = await sanity.fetch(`*[_id == $id][0]{
title, "slug": slug.current, contentType, section, market, language,
"author": author->name, campaignId
}`, {id: _id})
if (!page?.slug) return NextResponse.json({skipped: true})
const auth = new google.auth.GoogleAuth({
credentials: JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON!),
scopes: [
'https://www.googleapis.com/auth/tagmanager.edit.containers',
'https://www.googleapis.com/auth/tagmanager.publish'
]
})
const gtm = google.tagmanager({version: 'v2', auth})
const workspacePath = `accounts/${process.env.GTM_ACCOUNT_ID}/containers/${process.env.GTM_CONTAINER_ID}/workspaces/${process.env.GTM_WORKSPACE_ID}`
const name = `sanity.${page.slug}.metadata`
const value = JSON.stringify(page)
const {data} = await gtm.accounts.containers.workspaces.variables.list({parent: workspacePath})
const existing = data.variable?.find((v) => v.name === name)
const variable = {name, type: 'c', parameter: [{type: 'template', key: 'value', value}]}
if (existing?.variableId) {
await gtm.accounts.containers.workspaces.variables.update({
path: `${workspacePath}/variables/${existing.variableId}`,
requestBody: {...existing, ...variable}
})
} else {
await gtm.accounts.containers.workspaces.variables.create({
parent: workspacePath,
requestBody: variable
})
}
return NextResponse.json({synced: true, variable: name})
}How Sanity + Google Tag Manager works
Build your Google Tag Manager integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect Google Tag Manager to the content changes your analytics stack depends on.
Start building free โCMS approaches to Google Tag Manager
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Page-level analytics metadata | Often tied to templates, plugins, or custom fields that vary by page type. Teams may need extra code to normalize values for GTM. | Schema-as-code defines analytics fields in Sanity Studio, and the Content Lake exposes them as typed JSON for dataLayer and GTM. |
| Real-time sync on publish | Plugin hooks may work for simple sites, but custom publish flows and previews can make timing hard to control. | Webhooks or Functions can trigger on content mutations, fetch the latest published data, and call the GTM API without polling. |
| Field selection for GTM payloads | Tracking code often reads from rendered pages, template variables, or plugin settings, which can send inconsistent values. | GROQ can select exactly the fields GTM needs, including referenced author, topic, campaign, market, and locale data in one query. |
| A/B test and campaign metadata | Experiment IDs and campaign labels are often hardcoded in templates or tracked in spreadsheets outside the publishing flow. | Editors can work with campaign and experiment fields in Sanity Studio, while developers pass the same values to GTM and analytics destinations. |
| Governance and safety | Plugin-based tag changes can be fast, but permissions, audit trails, and environment control vary a lot. | Role-based access, audit logs, schema validation, Content Releases, and review workflows help teams control which tracking metadata reaches production. |
| Multi-channel tracking consistency | Web tracking may work, but mobile apps, microsites, and agent experiences often need separate metadata handling. | One structured back end can supply web, mobile, GTM, analytics tools, and AI agents with the same content metadata. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Google Analytics
Send structured content metadata from Sanity to GA4 so reports can break down performance by topic, author, market, campaign, and content type.
Sanity + Segment
Use Sanity fields to enrich Segment events with content context before routing them to analytics, warehouse, and activation tools.
Sanity + Amplitude
Connect content attributes from Sanity to Amplitude events so product teams can analyze how pages, campaigns, and variants affect user behavior.