
Google Tag Manager
- 87 installs
- 93 repo stars
- Updated May 14, 2026
- thatrebeccarae/claude-marketing
Helps with ai & agent building tasks.
About
google-tag-manager is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- google-tag-manager
- AI & Agent Building
- AI-coding skill
Google Tag Manager by the numbers
- 87 all-time installs (skills.sh)
- +8 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #4,982 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thatrebeccarae/claude-marketing --skill google-tag-managerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 87 |
|---|---|
| repo stars | ★ 93 |
| Last updated | May 14, 2026 |
| Repository | thatrebeccarae/claude-marketing ↗ |
What it does
Helps with ai & agent building tasks.
Files
Google Tag Manager
Expert-level guidance for Google Tag Manager — auditing containers, designing tag and trigger architecture, debugging firing issues, implementing consent mode v2, and advising on server-side tagging.
Install
git clone https://github.com/thatrebeccarae/claude-marketing.git && cp -r claude-marketing/skills/google-tag-manager ~/.claude/skills/Core Capabilities
Container Auditing and Optimization
Full container audit covering the six core areas: overview stats, tag inventory, trigger review, variable audit, folder organization, and naming convention assessment. Identifies unused tags, duplicate GA4 config tags, over-broad triggers on conversion tags, undefined variable paths, and consent mode gaps. Produces prioritized recommendations ranked by impact and effort.
Tag Architecture
Expert guidance on all common tag types:
- GA4 Configuration (`gaawc`) — one per domain, holds measurement ID (always via constant variable), enhanced conversions settings, and shared config. All GA4 event tags must reference this as their Configuration Tag.
- GA4 Event (`gaawe`) — fires individual events with parameters sourced from data layer variables. References the
gaawcconfig tag. - Google Ads Conversion Tracking — conversion ID + label, value and currency from data layer, deduplication tag ID for enhanced conversions.
- Google Ads Remarketing — conversion ID, custom parameters for audience building.
- Meta Pixel — base code + standard events (PageView, ViewContent, AddToCart, InitiateCheckout, Purchase). Use community template from Template Gallery rather than custom HTML.
- Custom HTML — unsandboxed JavaScript, creates CSP risk. Only use when no Template Gallery alternative exists.
- Community Templates — vetted third-party templates for Cookiebot, OneTrust, HotJar, Meta Pixel, LinkedIn Insight Tag, and more. Always prefer over Custom HTML.
Trigger Design
Matching triggers to tags with correct scoping is the single biggest factor in data quality. See REFERENCE.md for the full trigger type taxonomy. Key principles:
- Page Load triggers (Page View, DOM Ready, Window Loaded) for analytics tags that need the DOM
- Custom Event triggers for data layer events — exact match on event name
- Element Visibility for scroll depth and above-the-fold measurement
- Consent Initialization trigger for consent defaults — must fire before all measurement tags
- Never use All Pages trigger for conversion or remarketing tags
Variable Strategy
Data Layer Variables (v2) are the correct pattern for capturing tracking values. Key strategy:
- Use constant variables for all measurement IDs, API keys, and container IDs — one update propagates everywhere
- Data Layer Variables with dot-notation paths for nested ecommerce objects (
ecommerce.value,ecommerce.transaction_id) - Lookup Table variables for mapping internal category names to tracking values
- Custom JavaScript variables for derived values — note sandbox limitations (no DOM, no fetch, no async)
- Event Settings Variable to share a reusable set of GA4 event parameters across multiple event tags
Consent Mode v2
Four consent parameters control how Google tags behave when users deny consent:
analytics_storage— GA4, Google Analyticsad_storage— Google Ads cookie storagead_user_data— sending user data to Google Ads for matchingad_personalization— remarketing and personalization features
Advanced mode (recommended for DTC): Tags fire cookieless on deny, enabling Google's conversion modeling. Requires wait_for_update so the CMP has time to resolve user choice before tags fire.
Implementation sequence is order-sensitive — default consent must fire before any measurement tags load. See REFERENCE.md Consent Mode v2 section for full implementation details.
Server-Side GTM
Server-side GTM moves tag processing from the browser to a server container (GCP or alternative), improving data quality, blocking ad blocker impact, and enabling server enrichment. Key decision factors: ad blocker loss rate, privacy requirements, Meta CAPI needs, budget for hosting (~$30-50/mo on GCP), and technical capacity for setup.
The server container has no automatic access to browser consent state — consent must be explicitly passed from the browser in the request to the sGTM endpoint.
Debugging
- Preview mode — activates the GTM debugger in a paired browser tab. Left panel shows event timeline; right panel shows tag firing status, variable values, and data layer state at each event.
- Tag Assistant — integrated with GTM Preview. Consent tab shows consent parameter state at each event.
- Browser console —
window.dataLayerinspection, filter by event name - Network tab — GA4 collect requests, verify parameters, check
gcsparameter for consent state
Workflow: Full Container Audit
When asked to audit a GTM container:
1. Conversion Tracking Verification — Confirm GA4 config tag exists, measurement ID stored in a constant variable (not hardcoded), enhanced conversions enabled, Google Ads conversion tags present and correctly configured.
2. Tag Inventory — List all tags: name, type, status (paused/enabled), firing trigger, tag firing options (unlimited/once per page/once per event). Flag unused, paused, or duplicate tags.
3. Trigger Review — List all triggers with conditions. Flag any conversion or remarketing tag attached to an All Pages trigger. Check trigger conditions for URL specificity on conversion triggers.
4. Variable Audit — Confirm recommended built-in variables are enabled (Page URL, Page Path, Click Classes, Click ID, Form ID). Check all user-defined variables — are any returning undefined? Verify data layer variable paths match actual dataLayer push structure.
5. Organization Check — Is there a folder structure? Are workspaces named descriptively or all in "Default Workspace"? Review publish history — how many versions, how often, are descriptions informative?
6. Naming Convention Assessment — Do tag/trigger/variable names follow a consistent framework? Can a new team member understand the container? Flag inconsistency as a maintenance risk.
7. Consent Mode Check — Does a consent default tag exist firing on Consent Initialization? Is it configured with all four parameters? Does a CMP template exist? Is TCF v2.3 compatible? Verify in Preview mode Consent tab.
8. Prioritized Recommendations — Compile all findings. Rank by impact (revenue/data quality/compliance risk) and effort (Low/Medium/High). Lead with quick wins.
Hard Rules
Non-negotiable constraints in every recommendation:
1. Never recommend DOM scraping when data layer is available. CSS selector or JS variable sniffing is fragile. Data layer is the contract between dev and analytics. 2. Never recommend All Pages trigger for conversion tags. Conversion and remarketing tags on All Pages inflate costs and corrupt attribution. 3. Never recommend Custom HTML when a community template exists. Community templates are sandboxed and vetted. Custom HTML runs unsandboxed and creates CSP/security risk. 4. Always verify consent mode default fires before measurement tags load. Wrong initialization order means consent is not applied — a compliance failure. 5. Always recommend constant variables for measurement IDs. Never hardcode a measurement ID, conversion ID, or API key directly in a tag. One update should propagate everywhere. 6. Always flag deprecated Universal Analytics tags for removal. UA was fully sunsetted July 2024. Dead tags in a container are clutter and a warning sign about overall maintenance. 7. Always clear previous ecommerce data before a new ecommerce push. dataLayer.push({ ecommerce: null }) before each push prevents data from a prior event contaminating the next. 8. Never skip workspace isolation. One change set per workspace. Never work in Default Workspace. Named workspaces with descriptive names are the expert standard.
Related Tools
- `google-analytics` skill — For GA4 data analysis after GTM implementation. GTM configures the tags; the google-analytics skill covers metrics, dimensions, reports, and attribution analysis on the data those tags collect.
- `gtm-implementer` agent — For writing GTM changes via the GTM API. Handles workspace preflight, rate-limited variable/trigger/tag creation, and payload construction. The google-tag-manager skill is the analysis layer; the gtm-implementer agent is the execution layer. When a user asks to implement GTM changes (not just audit or advise), point them to the agent.
How to Use This Skill
Ask questions like:
- "Audit our GTM container — we think some tags are not firing correctly"
- "Set up consent mode v2 in GTM with Cookiebot. We sell to EU customers."
- "Our GA4 purchase event is firing but transaction value shows as undefined in DebugView"
- "Should we go server-side with GTM? We are losing about 15% of data to ad blockers"
- "Review our naming conventions and tell me what needs to change"
- "Our conversion tags are firing on every page — help me scope them correctly"
- "What triggers should we use for a Shopify store with GA4, Google Ads, and Meta Pixel?"
- "Debug why our Add to Cart event stopped firing"
For complete tag type reference, trigger taxonomy, variable types, consent mode v2 parameters, sGTM architecture details, naming conventions, and anti-patterns, see REFERENCE.md.
For worked examples of container audits, consent mode setup, tag firing debug, and sGTM advisory, see EXAMPLES.md.
GTM Skill — Examples
Realistic worked examples showing how to use the GTM skill for container audits, consent mode setup, tag firing debug, and server-side GTM advisory.
---
1. Full Container Audit
User Request:
"Audit our GTM container. We think some tags are not firing correctly and we are not sure our consent setup is right. We have GA4, Google Ads, and Meta Pixel running."
Analysis Steps: 1. Request the container export (JSON) or a screenshot of the tag/trigger/variable lists 2. Build tag inventory table — name, type, status, trigger, firing options 3. Build trigger inventory — name, type, conditions, which tags use it 4. Check variable list for undefined DLVs and missing constant patterns 5. Check for consent mode tags and their trigger assignments 6. Cross-reference tag types against trigger types to identify scoping issues 7. Compile findings by severity and produce prioritized recommendations
Sample Output:
Container Overview
| Metric | Value |
|---|---|
| Total tags | 18 |
| Enabled | 15 |
| Paused | 3 |
| Total triggers | 14 |
| Total variables | 22 |
| Workspaces | 2 active (Default Workspace + "agency-meta") |
| Last published | 6 weeks ago |
| Last publisher | agency@partner.com |
Tag Inventory
| Tag Name | Type | Status | Firing Trigger | Firing Options | Issue |
|---|---|---|---|---|---|
| Google Analytics - Universal Analytics | UA legacy | Enabled | All Pages | Unlimited | UA sunsetted July 2024 — dead tag |
| GA4 - Pageview | GA4 Config (gaawc) | Enabled | All Pages | Unlimited | Hardcoded measurement ID |
| GA4 - Pageview v2 | GA4 Config (gaawc) | Enabled | All Pages | Unlimited | Duplicate config tag |
| GA4 - Purchase | GA4 Event (gaawe) | Enabled | CE -- purchase | Once per event | Config tag refs duplicate (v2) |
| GA4 - Add to Cart | GA4 Event (gaawe) | Enabled | CE -- add_to_cart | Unlimited | OK |
| GA4 - View Item | GA4 Event (gaawe) | Paused | CE -- view_item | Unlimited | Paused — why? |
| Google Ads - Purchase Conversion | Ads Conversion | Enabled | CE -- purchase | Unlimited | Missing deduplication tag ID |
| Google Ads - Remarketing | Ads Remarketing | Enabled | All Pages | Unlimited | All Pages on remarketing tag |
| Meta Pixel - Base | Custom HTML | Enabled | All Pages | Unlimited | Should use Template Gallery |
| Meta Pixel - Purchase | Custom HTML | Enabled | CE -- purchase | Unlimited | Should use Template Gallery |
| Meta Pixel - ViewContent | Custom HTML | Enabled | PV -- all-pages | Unlimited | Over-broad trigger for purchase intent event |
| HotJar | Custom HTML | Enabled | All Pages | Unlimited | Template Gallery alternative exists |
| LinkedIn Insight | Custom HTML | Paused | All Pages | Unlimited | Template exists; update and re-enable |
| Consent Default | — | Missing | — | — | No consent mode configured |
| CMP Template | — | Missing | — | — | No CMP template installed |
Key Findings
Finding 1 — No Consent Mode (Compliance Critical) No consent default tag exists. No CMP template installed. GA4 and Google Ads tags fire unconditionally for all visitors including EU/EEA users. This is a compliance failure for GDPR and Google's EU User Consent Policy. Google Ads conversion modeling is also unavailable without consent mode v2.
Finding 2 — Duplicate GA4 Configuration Tags Two gaawc tags exist: "GA4 - Pageview" (older, hardcoded measurement ID) and "GA4 - Pageview v2" (same trigger). Duplicate config tags can cause double-counting of pageviews and session inflation. The purchase event tag references the newer one; other event tags may reference either. Remove the older tag and migrate all references to one config tag.
Finding 3 — Dead Universal Analytics Tag "Google Analytics - Universal Analytics" is still enabled and firing on All Pages. UA was fully sunsetted July 2024 — this tag sends data to a dead property. It adds page load overhead with no benefit. Delete it.
Finding 4 — Meta Pixel via Custom HTML (Three Tags) All Meta Pixel tags use Custom HTML. The Meta Pixel community template exists in the Template Gallery and is consent-mode aware. Custom HTML runs unsandboxed, creates CSP risk, and does not automatically respond to consent signals. Replace all three with the official Meta Pixel template.
Finding 5 — Google Ads Remarketing on All Pages Remarketing tag fires on every page view. Recommended: add page type parameter to segment by funnel stage (home, category, product, cart, checkout, purchase). Firing on all pages with no segmentation limits audience list quality.
Finding 6 — Hardcoded Measurement IDs GA4 config tag has the measurement ID hardcoded in the tag settings. Create a Const -- GA4 Measurement ID variable and reference it. Same pattern needed for Google Ads Conversion ID.
Prioritized Recommendations
| Priority | Action | Impact | Effort |
|---|---|---|---|
| 1 | Implement consent mode v2 (consent default + CMP template) | Compliance risk eliminated; modeling enabled | Medium |
| 2 | Remove duplicate GA4 config tag and dead UA tag | Data accuracy; container hygiene | Low |
| 3 | Replace Custom HTML Meta Pixel tags with Template Gallery | Security; consent signal integration | Medium |
| 4 | Add deduplication tag ID to Google Ads conversion tag | Prevents duplicate conversion reporting | Low |
| 5 | Move measurement IDs to constant variables | Maintainability | Low |
| 6 | Scope Google Ads Remarketing trigger to page types | Audience quality improvement | Low |
| 7 | Investigate and re-enable paused View Item tag | Missing funnel data for add-to-cart optimization | Low |
---
2. Consent Mode v2 Setup
User Request:
"We sell to EU customers and need consent mode set up in GTM. We use Cookiebot as our CMP. We have GA4 and Google Ads. Currently no consent mode is configured."
Analysis Steps: 1. Confirm current state — no consent default, no CMP template, tags fire unconditionally 2. Identify the Cookiebot community template in GTM Template Gallery 3. Define implementation sequence (consent default before everything) 4. Configure region-specific defaults for EU vs non-EU 5. Verify existing GA4 and Google Ads tags have built-in consent checks enabled 6. Provide verification steps using Preview mode Consent tab
Current State
No consent mode configured. GA4 and Google Ads tags fire on All Pages trigger with no consent checks. All EU/EEA visitors receive full tracking without a consent signal.
Step-by-Step Implementation
Step 1: Create Consent Initialization trigger
- Go to Triggers → New
- Trigger Type: Consent Initialization
- Name:
Consent -- init - No conditions (fires for all pages)
Step 2: Create Consent Default tag
- Go to Tags → New
- Tag Type: Custom HTML
- Name:
Consent -- Default - Firing Trigger:
Consent -- init(from Step 1)
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// EU/EEA visitors: all denied by default
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500,
'region': ['AT','BE','BG','CY','CZ','DE','DK','EE','ES','FI',
'FR','GR','HR','HU','IE','IS','IT','LI','LT','LU',
'LV','MT','NL','NO','PL','PT','RO','SE','SI','SK']
});
// All other regions: all granted by default
gtag('consent', 'default', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});
// Preserve ad click data when ad_storage denied
gtag('set', 'url_passthrough', true);
gtag('set', 'ads_data_redaction', true);
</script>Step 3: Install Cookiebot CMP community template
- Go to Templates → Search Gallery
- Search "Cookiebot"
- Click "Cookiebot CMP" (published by Cookiebot)
- Add to workspace
Step 4: Create Cookiebot CMP tag
- Go to Tags → New
- Tag Type: Cookiebot CMP (from gallery)
- Name:
CMP -- Cookiebot -- All Pages - Cookiebot Domain Group ID:
[your domain group ID from Cookiebot dashboard] - Mode: Automatic (recommended)
- Firing Trigger: Page View - All Pages
Step 5: Verify existing GA4 and Google Ads tags
- Open each GA4 and Google Ads tag
- Under Advanced Settings → Consent Settings
- Confirm "Require additional consent for tag to fire" is NOT set (built-in consent checks handle this automatically for GA4/Ads tags when consent mode is active)
Step 6: Publish and verify in Preview mode 1. Click Preview in GTM 2. Navigate to site in paired browser window 3. Click the "Consent" tab in Tag Assistant 4. Check consent state at each event
Verification Checklist
| Check | Pass Criteria |
|---|---|
| Consent Default fires first | "Consent -- Default" appears before all other tags in event timeline |
| All four parameters present | analytics_storage, ad_storage, ad_user_data, ad_personalization all show in Consent tab |
| EU visitor — banner shown, deny | Consent tab shows all four parameters as denied |
| EU visitor — banner accepted | Consent tab shows all four parameters as granted |
| Non-EU visitor | Consent tab shows all four parameters as granted (no banner needed) |
| GA4 tag consent check | GA4 collect request shows gcs=G100 on deny, gcs=G111 on grant |
| CMP template fires | Cookiebot tag shows as Fired in Tags tab |
Expected Behavior After Implementation
| Visitor | Scenario | GA4 Data | Google Ads Modeling |
|---|---|---|---|
| EU visitor | Denies consent | Cookieless pings only (Advanced mode) — no personal data | Google models conversions from anonymous signals |
| EU visitor | Accepts consent | Full tracking | Full attribution |
| Non-EU visitor | No banner | Full tracking | Full attribution |
| EU visitor | Ignores banner (timeout) | wait_for_update: 500ms expires → denied defaults apply | Cookieless pings |
---
3. Tag Firing Debug — Undefined Purchase Value
User Request:
"Our GA4 purchase event is firing but the transaction value shows as undefined in DebugView. The tag is definitely firing — I can see it in Preview mode. The purchase tag worked fine until two weeks ago."
Analysis Steps: 1. Ask diagnostic questions to narrow the scope 2. Walk through Preview mode systematically at the purchase event 3. Identify the variable path mismatch 4. Confirm fix and verify in Preview mode
Diagnostic Questions
Before debugging: clarify the environment.
- What platform is the site on? (Shopify, custom, headless)
- Is this a recent code change on the site two weeks ago?
- What does the Data Layer tab show for the purchase event in Preview mode?
- What is the Data Layer Variable path configured for the value parameter?
Preview Mode Walkthrough
Step 1: Activate Preview mode
- Click Preview in GTM (top right)
- Navigate to the store and complete a test purchase (or use order confirmation URL directly)
Step 2: Find the purchase event in the timeline
- Left panel — look for
purchasecustom event in the event list - If not visible, check that the
dataLayer.push({ event: 'purchase', ... })is firing on the page
Step 3: Check the Data Layer tab at the purchase event
- Click the
purchaseevent in the left panel - Click the Data Layer tab in the right panel
- Look for the
ecommerceobject
What you should see (GA4 format):
ecommerce: {
transaction_id: "T12345",
value: 89.99,
currency: "USD",
items: [...]
}What may be present instead (UA legacy format):
ecommerce: {
purchase: {
actionField: {
id: "T12345",
revenue: "89.99"
},
products: [...]
}
}Step 4: Check the Variables tab at the purchase event
- Click Variables tab (same event selected)
- Find the DLV variable used in the GA4 purchase tag value parameter
- Read the value it resolved to
If it shows undefined, the variable path does not match the actual data layer structure.
Step 5: Open the GA4 purchase tag and check the value parameter
- Go to GA4 Purchase tag settings
- Check the value parameter — which variable is it referencing?
- Open that variable — what is the Data Layer Variable Name configured?
Root Cause Identified
The Data Layer Variable path is configured as ecommerce.purchase.value (Universal Analytics nested format). Two weeks ago, the development team updated the ecommerce data layer pushes to GA4 format. The GA4 format uses ecommerce.value (flat), not ecommerce.purchase.value (nested UA format).
The tag fires successfully — it just sends undefined for the value parameter because the variable path no longer matches the data layer structure.
Fix
1. Open the Data Layer Variable used for purchase value 2. Change the Data Layer Variable Name from:
ecommerce.purchase.value(old UA format)
3. To:
ecommerce.value(GA4 flat format)
4. Check the same issue for related variables:
ecommerce.purchase.actionField.id→ecommerce.transaction_idecommerce.purchase.actionField.revenue→ecommerce.valueecommerce.products→ecommerce.items
Verify the Fix
1. Go back to Preview mode 2. Navigate to order confirmation page 3. Click purchase event in timeline 4. Variables tab — confirm the value DLV now shows a numeric value (not undefined) 5. Tags tab — confirm GA4 purchase tag fired 6. Network tab — find the GA4 collect request, confirm epn.value parameter has the correct value
Prevention Note
When the development team updates ecommerce data layer schema (UA → GA4 migration is common), GTM variable paths must be updated to match. Maintain a Solution Design Reference (SDR) document mapping every GTM variable to its data layer path — this makes migrations visible and auditable.
---
4. Server-Side GTM Advisory
User Request:
"We are losing about 15-20% of our GA4 data to ad blockers and our Meta CAPI is not set up. Should we go server-side with GTM? We're a DTC brand on Shopify, about $2M annual revenue, two-person marketing team."
Analysis Steps: 1. Evaluate the decision criteria against their situation 2. Provide architecture recommendation 3. Clarify what moves server-side vs stays client-side 4. Explain consent state passing requirement 5. Outline implementation phases with effort estimate
Decision Framework
| Factor | Their Situation | Weight |
|---|---|---|
| Ad blocker loss | 15-20% of GA4 data — significant | High |
| Privacy requirements | Shopify DTC, EU traffic but not primary market | Medium |
| Meta CAPI need | Not set up — sGTM enables this | High |
| Budget | $2M revenue — $30-50/mo hosting is viable | Low concern |
| Technical capacity | 2-person marketing team — no dedicated engineer | Medium concern |
| Data enrichment needs | Standard DTC tracking — no special server signals | Low |
Recommendation: Yes, go server-side — hybrid approach
Server-side GTM is appropriate for this situation. The primary drivers are: 1. 15-20% ad blocker data loss is recoverable — sGTM requests come from a server IP, not the browser, so ad blockers cannot intercept them 2. Meta CAPI requires server-to-server calls — sGTM's server container handles this natively without custom API code 3. Shopify DTC at $2M revenue can absorb the $30-50/month hosting cost with meaningful ROI from improved data quality
The 2-person team concern is real but manageable. Stape.io (managed sGTM hosting) removes infrastructure complexity and is worth the additional cost.
Hybrid Architecture
Not everything moves server-side. The recommended split:
| Stays Client-Side | Moves Server-Side |
|---|---|
| Consent mode default + CMP | GA4 event forwarding |
| GTM Preview/debug mode | Google Ads conversion tracking |
| Any tag needing DOM access | Meta CAPI (Conversions API) |
| Core Web Vitals tags | Server-side data enrichment (if needed later) |
| First-party cookie reads |
Architecture Diagram
Browser (Shopify storefront)
|
+-- Client-side GTM container (web)
|
+-- Consent default + Cookiebot CMP
|
+-- GA4 measurement (sends to sGTM endpoint)
|
+-- sGTM forwarding tag (sends events to sGTM)
|
+-- sGTM Endpoint (Stape.io or GCP)
|
+-- GA4 Client (receives events)
|
+-- Server container
|
+-- GA4 tag → GA4 property
+-- Google Ads tag → Google Ads
+-- Meta CAPI tag → Meta Events APICritical: Consent State Passing
The server container has no automatic access to the user's consent choice. It must be explicitly passed.
Required: Include consent state parameters in the request from the client-side container to the sGTM endpoint. The sGTM GA4 Client forwards consent state from the incoming request. Verify that the data stream configuration in GA4 has consent mode integration enabled.
Without this, the server container fires tags for users who have denied consent — a compliance failure.
Implementation Phases
| Phase | Work | Effort |
|---|---|---|
| 1 | Set up sGTM container in Stape.io, configure GCP connection | 2-3 hours |
| 2 | Add sGTM forwarding tag to client-side container, test in Preview | 2-3 hours |
| 3 | Configure GA4 client in server container, verify GA4 data flows | 2-3 hours |
| 4 | Set up Meta CAPI server tag — Pixel ID + access token, event deduplication | 3-4 hours |
| 5 | Configure Google Ads server tag, deduplication from client-side | 2 hours |
| 6 | QA: compare client-side vs server-side hit counts, verify consent passing | 2-3 hours |
Total estimated setup: 13-18 hours one-time, best split across 2-3 sessions
Cost Estimate
| Option | Monthly Cost | Notes |
|---|---|---|
| Stape.io Starter | $9/mo | Limited to 300K events/mo — suitable for lower-volume DTC |
| Stape.io Pro | $49/mo | 3M events/mo — suitable for most DTC at $2M revenue |
| GCP App Engine (self-managed) | ~$30-50/mo | 3 instances; requires GCP setup comfort |
Expected Outcome After Implementation
- GA4 data loss from ad blockers: 15-20% → approximately 2-5% (some browsers block even first-party endpoints)
- Meta CAPI event match quality: new baseline — typically 70-85% match rate vs 40-60% pixel-only
- Google Ads conversion modeling: improved signal quality with server-side deduplication
- Data quality improvement generally worth the setup cost within the first quarter
MIT License
Copyright (c) 2026 Rebecca Rae Barton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
GTM Reference
Container Hierarchy
Account
+-- Container (Web / Server / iOS / Android / AMP)
+-- Workspace (max 3 on free, unlimited on 360)
|-- Tags
|-- Triggers
|-- Variables
+-- Folders- Account: Organization-level (tied to a Google account)
- Container: A deployable GTM unit for a specific platform. One container snippet per site.
- Workspace: A draft environment for making changes before publishing. Free accounts: 3 max. 360: unlimited.
- Published Version: A snapshot of the container at the time of publish. Every publish creates a numbered version.
---
Tag Types
GA4 Configuration (gaawc)
| Field | Value |
|---|---|
| GTM type code | gaawc |
| Display name | Google Tag / GA4 Configuration |
| Purpose | Holds GA4 measurement ID, shared config settings, enhanced conversions toggle |
| Required config | Measurement ID (always via constant variable), tag ID |
| Common mistakes | Duplicate config tags (one per domain max), hardcoded measurement ID, missing enhanced conversions |
| Notes | All GA4 event tags must reference this as Configuration Tag. Introduced as "Google tag" in 2023. |
GA4 Event (gaawe)
| Field | Value |
|---|---|
| GTM type code | gaawe |
| Display name | GA4 Event |
| Purpose | Fires individual GA4 events with parameters |
| Required config | Event name, Configuration Tag reference (points to gaawc), event parameters |
| Common mistakes | Missing Configuration Tag reference, event parameters pulling from wrong variable path, sending UA-style parameters |
| Notes | Event name should match GA4 recommended event names (purchase, add_to_cart, view_item, etc.) where applicable |
Google Ads Conversion Tracking
| Field | Value |
|---|---|
| GTM type code | awct |
| Display name | Google Ads Conversion Tracking |
| Purpose | Records conversions for bidding optimization and reporting |
| Required config | Conversion ID, Conversion Label, Conversion Value, Currency, Conversion Deduplication Tag ID |
| Common mistakes | Firing on All Pages (inflates conversions), hardcoded value instead of data layer variable, missing deduplication |
| Notes | Enhanced conversions requires first-party data fields (email, phone, name). Deduplication ID prevents duplicate conversions from same transaction. |
Google Ads Remarketing
| Field | Value |
|---|---|
| GTM type code | sp |
| Display name | Google Ads Remarketing |
| Purpose | Builds remarketing audiences, sends custom parameters for smart bidding signals |
| Required config | Conversion ID, custom parameters (page type, product IDs, value) |
| Common mistakes | Missing ecommerce custom parameters, firing on All Pages without page type parameter |
| Notes | Dynamic remarketing requires product IDs from merchant center feed |
Meta Pixel
| Field | Value |
|---|---|
| GTM type code | Community template (preferred) or Custom HTML |
| Display name | Meta Pixel / Facebook Pixel |
| Purpose | Base code + standard events for Meta advertising attribution and audience building |
| Required config | Pixel ID, standard event name (PageView, ViewContent, AddToCart, InitiateCheckout, Purchase), event parameters |
| Common mistakes | Custom HTML instead of community template, missing deduplication with server-side CAPI, sending PII without hashing |
| Notes | Use the official Meta Pixel community template from Template Gallery. Combine with Meta CAPI via sGTM for deduplication. |
Custom HTML
| Field | Value |
|---|---|
| GTM type code | html |
| Display name | Custom HTML |
| Purpose | Runs arbitrary unsandboxed JavaScript — for tags with no Template Gallery equivalent |
| Required config | HTML/JavaScript code, Support document.write option (legacy, avoid) |
| Common mistakes | Using Custom HTML when a template exists, injecting PII, bypassing CSP, no error handling |
| Notes | AVOID: runs unsandboxed, CSP violations, security audit failures. Always check Template Gallery first. |
Custom Image
| Field | Value |
|---|---|
| GTM type code | img |
| Display name | Custom Image |
| Purpose | 1x1 pixel tracking — for ad network pixels without a JavaScript tag |
| Required config | Image URL (with query parameters for data passing) |
| Common mistakes | Using for modern platforms that have JS tags, missing consent controls |
| Notes | Legacy pattern. Most modern ad platforms have JS tags or sGTM clients. |
Community Templates
Templates from the GTM Template Gallery, sandboxed and vetted by Google. Preferred over Custom HTML for any third-party tool.
| Common Templates | Use Case |
|---|---|
| Cookiebot CMP | Consent mode v2 with Cookiebot integration |
| OneTrust | Consent mode v2 with OneTrust integration |
| TrustCommander | Consent mode v2 with Commanders Act |
| LinkedIn Insight Tag | LinkedIn audience tracking |
| Meta Pixel (official) | Meta advertising and audiences |
| HotJar | Session recording and heatmaps |
| Clarity | Microsoft Clarity session recording |
| Pinterest Tag | Pinterest audience tracking |
| Snapchat Pixel | Snapchat advertising |
---
Trigger Types
Page Load Triggers
| Trigger | Fires When | Common Use Case | Scoping Tips |
|---|---|---|---|
| Consent Initialization | Before any other trigger, including Page View | Consent mode default tag, CMP initialization | Must fire first — use only for consent setup |
| Initialization | Before all other triggers, after Consent Initialization | GA4 global config, linker initialization | Rarely needed; prefer Page View for most cases |
| Page View | GTM container finishes loading | GA4 pageview, basic analytics | Scope by Page URL contains/matches for conversion pages |
| DOM Ready | DOM fully parsed, before images load | Tags that query DOM elements | Use instead of Page View when tag needs DOM access |
| Window Loaded | All page resources loaded (images, scripts) | Chat widgets, lazy-loaded content, scroll tracking setup | Slowest; only when needed |
Click Triggers
| Trigger | Fires When | Common Use Case | Scoping Tips |
|---|---|---|---|
| All Elements | Any click on any element | Button clicks, icon clicks, non-link elements | Scope with Click Classes, Click ID, or Click Element |
| Just Links | Click on <a> link elements | External link tracking, download tracking | Enable built-in Click URL variable; scope by Click URL |
User Engagement Triggers
| Trigger | Fires When | Common Use Case | Scoping Tips |
|---|---|---|---|
| Element Visibility | Specified element enters/exits viewport | Above-fold measurement, CTA visibility, lazy content | Use CSS selector, set threshold %; fire once per element |
| Form Submission | Form submit event | Lead form tracking, newsletter signup | Enable built-in Form ID variable; scope by Form ID |
| Scroll Depth | User scrolls to specified % or pixel depth | Engagement scoring, content read depth | Set % thresholds (25/50/75/90); once per page |
| YouTube Video | YT video starts, pauses, completes, reaches % | Video engagement tracking | Requires YouTube JS API support on embedded video |
Other Triggers
| Trigger | Fires When | Common Use Case | Scoping Tips |
|---|---|---|---|
| Custom Event | dataLayer.push({ event: 'event-name' }) | Ecommerce events, user interactions, SPA navigation | Exact match (preferred) or regex on event name |
| History Change | Browser history state changes | Single-page application (SPA) pageview tracking | Use for React/Vue/Angular SPAs |
| JavaScript Error | Uncaught JS error on page | Error monitoring, developer debugging | Filter by error message; be careful about volume |
| Timer | After specified interval | Content engagement timing, survey triggers | Set interval + limit; creates many events if misused |
---
Variable Types
Built-In Variables
Enable in Variables > Configure. Enable only what you need — each active variable adds overhead.
| Variable | What It Returns | When to Enable |
|---|---|---|
| Page URL | Full page URL including query string | URL-based triggers, UTM capture |
| Page Hostname | Domain only (example.com) | Cross-domain filtering |
| Page Path | Path portion (/products/slug) | Page-specific trigger conditions |
| Referrer | Previous page URL | Attribution debugging |
| Click Element | DOM element that was clicked | Click trigger conditions |
| Click Classes | CSS classes of clicked element | Scope click triggers by class name |
| Click ID | ID attribute of clicked element | Scope click triggers by element ID |
| Click Target | Target attribute (\_blank etc.) | External link detection |
| Click URL | href of clicked link | Link click destination |
| Click Text | Text content of clicked element | Text-based click conditions |
| Form Element | DOM form element | Form trigger conditions |
| Form Classes | CSS classes of form | Form scope by class |
| Form ID | ID attribute of form | Form scope by ID |
| Form Target | Form target attribute | Form conditions |
| Form Text | Form submit button text | Form conditions |
| Form URL | Form action URL | Form destination |
| Scroll Depth Threshold | Scroll % or px value at trigger time | Scroll depth event parameters |
| Scroll Depth Units | percent or pixels | Scroll event parameters |
| Video Current Time | Video playback position (seconds) | YouTube event parameters |
| Video Duration | Total video length (seconds) | YouTube event parameters |
| Video Percent | Playback % at trigger time | YouTube event parameters |
| Video Provider | Provider name (youtube) | YouTube event conditions |
| Video Status | playing, paused, ended | YouTube event parameters |
| Video Title | YouTube video title | YouTube event parameters |
| Video URL | YouTube video URL | YouTube event parameters |
| Video Visible | Boolean — video in viewport | Visibility conditions |
| Environment Name | live, staging, etc. | Environment-based conditions |
Data Layer Variables (v2)
Access values pushed to window.dataLayer via dot-notation paths.
| Config | Notes |
|---|---|
| Data Layer Variable Name | Path to value: ecommerce.value, user.email, product.category |
| Version | Always use Version 2. Version 1 does not support nested object access. |
| Default Value | Set to prevent undefined in tag parameters — use empty string or 0 |
Common DLV paths for ecommerce:
ecommerce.transaction_id → purchase transaction ID
ecommerce.value → order value
ecommerce.currency → currency code (USD, EUR)
ecommerce.items → items array (pass to GA4 items parameter)
ecommerce.coupon → coupon codeNote: GA4 ecommerce schema uses flat ecommerce.value (NOT ecommerce.purchase.value — that is the old UA format).
URL Variables
| Component | Returns | Example |
|---|---|---|
protocol | https | https |
host | Hostname | www.example.com |
port | Port number | 443 |
path | URL path | /products/widget |
query | Query string | utm_source=google&gclid=... |
fragment | Hash fragment | section-2 |
query key | Specific query param value | URL variable with key utm_source |
DOM Element Variable
Reads a DOM attribute from a CSS-selected element.
| Field | Notes |
|---|---|
| Element Selector | CSS selector (e.g., #product-price) |
| Attribute Name | innerText, value, data-price, or any HTML attribute |
| Warning | Fragile — DOM structure changes break the variable. Prefer data layer. |
JavaScript Variable
Returns the value of a global JavaScript variable on the page.
| Field | Notes |
|---|---|
| Global Variable Name | Dot-notation supported: shopify.order.total |
| Warning | Only works if the variable exists on window. Race conditions if variable set async. |
Custom JavaScript Variable
A sandboxed JavaScript function that returns a value.
| Sandbox Limitations | Notes |
|---|---|
| No DOM access | Cannot use document.querySelector |
No window or location | Limited browser API access |
| No async / fetch | Synchronous execution only |
| Return value required | Must return a value |
Constant Variable
Single static value. Best practice for all IDs and shared config.
Const - GA4 Measurement ID → G-XXXXXXXX
Const - Google Ads Conv ID → AW-XXXXXXXXX
Const - Meta Pixel ID → 1234567890
Const - GTM Container ID → GTM-XXXXXXLookup Table Variable
Maps an input variable value to an output value.
| Use Case | Example |
|---|---|
| Page type mapping | Page Path /checkout → "checkout", /thank-you → "purchase" |
| Category mapping | Internal category name → GA4 item_category value |
| Language mapping | Browser language → regional configuration |
Regex Table Variable
Pattern-based version of Lookup Table. Input is matched against regexes.
| Use Case | Example |
|---|---|
| URL pattern → page type | /products/.* → "product detail page" |
| Multiple URL patterns | `/(cart |
Event Settings Variable
Reusable set of GA4 event parameters shared across multiple GA4 event tags.
| Use Case | Notes |
|---|---|
| Shared parameters | user_id, session_id, currency shared across all events |
| Reduces duplication | Define once, reference in N event tags |
| Merge behavior | Tag-level parameters override Event Settings values |
Google Tag Configuration Variable
Shared Google tag configuration (measurement ID, ads optimization settings) that can be referenced by multiple Google tags.
---
Consent Mode v2 Reference
Four Consent Parameters
| Parameter | Controls | Default Recommendation |
|---|---|---|
analytics_storage | GA4, Google Analytics cookies and measurement | denied (EU), granted (others) |
ad_storage | Google Ads cookie storage for click attribution | denied (EU), granted (others) |
ad_user_data | Sending user data to Google for ad matching | denied (EU), granted (others) |
ad_personalization | Remarketing and personalized advertising | denied (EU), granted (others) |
Basic vs Advanced Mode
| Basic Mode | Advanced Mode | |
|---|---|---|
| On user deny | Tags do NOT fire | Tags fire cookieless (no identifiers sent) |
| Conversion modeling | Not available | Available — Google models missing conversions |
| Cookieless pings | No | Yes — anonymous signals when consent denied |
| EU traffic impact | Complete data loss for deniers | Partial recovery via modeling |
| Recommended for DTC | No | Yes — preserves modeling capability |
Implementation Sequence (Order-Sensitive)
The order of these steps is critical. Consent default MUST fire before measurement tags load.
1. Consent Initialization trigger → Consent Default tag
(fires before all other triggers)
2. GTM container loads and fires remaining tags:
→ CMP template tag (loads Cookiebot, OneTrust, etc.)
→ CMP reads browser storage / asks user
3. User makes choice (accept / decline / customize)
→ CMP calls gtag('consent', 'update', {...})
→ GA4, Ads tags receive updated consent stateConsent Default Tag Configuration
// Fires on: Consent Initialization trigger
// This tag runs BEFORE everything else
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500 // ms — time for CMP to call update
});Region-Specific Defaults
// Stricter defaults for EU/EEA; permissive for others
gtag('consent', 'default', {
'analytics_storage': 'denied',
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'region': ['AT','BE','BG','CY','CZ','DE','DK','EE','ES','FI',
'FR','GR','HR','HU','IE','IS','IT','LI','LT','LU',
'LV','MT','NL','NO','PL','PT','RO','SE','SI','SK']
});
gtag('consent', 'default', {
'analytics_storage': 'granted',
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted'
// No region = applies to all other regions
});URL Passthrough and Ads Data Redaction
// Preserves ad click data in URLs when ad_storage is denied
gtag('set', 'url_passthrough', true);
// Redacts ads-related data from requests when ad_storage is denied
gtag('set', 'ads_data_redaction', true);TCF Compliance
- TCF v2.3 is current as of February 28, 2026 (replaced v2.2)
- Verify CMP template in GTM is TCF v2.3 compatible
- Flag any CMP still on v2.2 as a compliance risk
Consent State in Network Requests
The gcs parameter in GA4 collect requests shows consent state:
| gcs Value | Meaning |
|---|---|
G100 | analytics_storage: denied, ad_storage: denied |
G110 | analytics_storage: denied, ad_storage: granted |
G101 | analytics_storage: granted, ad_storage: denied |
G111 | analytics_storage: granted, ad_storage: granted |
---
Server-Side GTM Architecture
Architecture Overview
Browser
|
+-- Client-side GTM container
|
+-- Sends HTTP request to sGTM endpoint
|
+-- Server Container
|
+-- Clients (normalize incoming request)
| |-- GA4 Client (handles GA4/gtag requests)
| +-- Measurement Protocol Client
|
+-- Triggers (event-based processing)
|
+-- Variables (read from event data)
|
+-- Tags (forward to destinations)
|-- GA4 Measurement Protocol
|-- Google Ads Conversion Tracking
|-- Meta CAPI (Conversions API)
+-- Custom HTTP requestsClients
Clients are adapters that receive incoming requests and normalize them into events the server container can process.
| Client | What It Handles |
|---|---|
| GA4 Client | Requests from GA4/gtag.js — the primary client for most implementations |
| Measurement Protocol Client | Server-to-server requests using Measurement Protocol v2 |
| Custom Client | Process any custom HTTP request format (Shopify webhooks, etc.) |
Server Container vs Client-Side Container
| Capability | Client-Side | Server-Side |
|---|---|---|
| DOM access | Yes | No |
| Browser APIs (cookies, storage) | Yes | No — server only sees request data |
| Ad blocker bypass | No | Yes — request from server IP |
| Consent visibility | Automatic | Must be passed explicitly in request |
| Tag Templates | Full library | Subset; growing |
| Hosting | Free (browser) | ~$30–50/mo on GCP |
| Data enrichment | Limited to browser data | Can add server-side signals (CRM lookup, etc.) |
Consent State in sGTM
Critical: The server has no automatic access to the user's consent choice. Consent lives in the browser.
Required pattern: 1. Browser reads consent state from CMP 2. Browser includes consent state in request to sGTM endpoint (as request parameter or event data field) 3. sGTM Client reads consent parameter from incoming request 4. Server-side variable reads consent state 5. Server-side tag trigger conditions check consent state before firing
Hosting Options
| Option | Cost (est.) | Managed | Best For |
|---|---|---|---|
| GCP App Engine | ~$30–50/mo (3 instances) | Partially | Most implementations; Google-recommended |
| Stape.io | $9–79/mo | Fully | Agencies, teams without GCP expertise |
| AWS (official pattern) | Variable | Partially | AWS-first infrastructure |
| Self-hosted Docker | Infrastructure cost only | No | Advanced teams with existing infra |
What Moves Server-Side vs Stays Client-Side
| Stay Client-Side | Move Server-Side |
|---|---|
| Consent mode default/update | GA4 event forwarding |
| CMP template tag | Google Ads conversion tracking |
| GTM Preview/debug tags | Meta CAPI |
| Tags requiring DOM access | Any server enrichment |
| Core Web Vitals tags | Data warehouse forwarding |
---
Naming Convention Framework
Consistent naming makes containers readable and maintainable by anyone.
Tag Naming
Pattern: {Type} -- {Event/Purpose} -- {Location}
GA4 -- PageView -- All Pages
GA4 -- Purchase -- Order Confirmation
GA4 -- AddToCart -- PDP
GA4 -- Config -- All Pages
Google Ads -- Conversion Purchase -- Order Confirmation
Google Ads -- Remarketing -- All Pages
Meta Pixel -- Purchase -- Order Confirmation
Meta Pixel -- ViewContent -- PDP
LinkedIn -- PageView -- All Pages
HotJar -- Init -- All PagesTrigger Naming
Pattern: {TriggerType} -- {Location/Condition}
CE -- purchase (Custom Event, event name: purchase)
CE -- add_to_cart (Custom Event, event name: add_to_cart)
CE -- view_item (Custom Event, event name: view_item)
PV -- order-confirmation (Page View, URL: /order-confirmation)
PV -- all-pages (Page View, no conditions)
PV -- pdp (Page View, URL matches /products/*)
Click -- add-to-cart-button (Click, element: .add-to-cart)
Form -- newsletter-signup (Form, form ID: newsletter-form)
Scroll -- 50pct (Scroll Depth, threshold: 50%)
Visibility -- above-fold (Element Visibility, element: .hero)
Consent -- init (Consent Initialization, no conditions)Variable Naming
Pattern: {Type} -- {Name}
DLV -- ecommerce.value (Data Layer Variable)
DLV -- ecommerce.transaction_id
DLV -- ecommerce.currency
DLV -- ecommerce.items
DLV -- user.email
Const -- GA4 Measurement ID (Constant)
Const -- Google Ads Conv ID
Const -- Meta Pixel ID
URL -- Page Path (URL Variable)
URL -- Page Hostname
JS -- User Login State (Custom JavaScript)
LT -- Page Type (Lookup Table)
RT -- URL Category (Regex Table)
DOM -- Product Price (DOM Element — use sparingly)Workspace Naming
Pattern: {owner-initials}-{change-description}
rr-ga4-aug2026
agency-consent-mode-setup
dev-enhanced-conversions
claude-audit-fixes-2026---
Anti-Patterns
1. DOM Scraping Instead of Data Layer
What: Using DOM Element variables or Custom JS to read values from the page HTML (prices, product names, category).
Why it fails: DOM structure changes with design updates. CSS selectors break silently. Race conditions if the element isn't rendered when the tag fires.
Do instead: Push the values to window.dataLayer in the page source or via ecommerce events. Data layer is the stable contract between engineering and analytics.
---
2. All Pages Trigger on Conversion Tags
What: A conversion tag (Google Ads conversion, Meta Pixel purchase event) attached to an All Pages trigger or a Page View trigger without URL conditions.
Why it fails: Fires on every page load. Conversion counts massively exceed actual transactions. Bidding algorithms optimize toward phantom conversions. ROAS looks artificially high.
Do instead: Scope conversion tags to the specific confirmation page URL. Use Custom Event trigger matching the purchase event name pushed from the site.
---
3. Custom HTML When a Template Exists
What: Building a Custom HTML tag for a third-party tool (Meta Pixel, HotJar, LinkedIn) when the GTM Template Gallery has an official or community template.
Why it fails: Custom HTML runs unsandboxed. Content Security Policy violations. No security review. Harder to maintain. Templates handle consent signal integration automatically.
Do instead: Search Template Gallery first. 95% of common tools have a template. Install from gallery — sandboxed, maintainable, often consent-mode aware.
---
4. Missing or Late Consent Defaults
What: No consent default tag, or the consent default fires after the GA4/Ads tags load.
Why it fails: Without a default, Google tags assume all consent granted — a compliance violation in EU/EEA. If the default fires after measurement tags, the measurement data was collected before consent was applied.
Do instead: Create a tag on the Consent Initialization trigger (fires before everything else) setting all four parameters to denied with wait_for_update: 500. Verify in Preview mode Consent tab.
---
5. Hardcoded Measurement IDs
What: Typing the GA4 measurement ID (G-XXXXXXXX), Google Ads conversion ID, or other IDs directly into each tag that needs them.
Why it fails: When an ID changes, every tag must be updated individually. Errors are common. One tag with the old ID breaks attribution.
Do instead: Create a Constant variable (Const -- GA4 Measurement ID). Reference the variable in all tags. One update propagates everywhere.
---
6. Single Shared Workspace
What: All team members (and agencies) working directly in Default Workspace, publishing over each other.
Why it fails: GTM has no conflict resolution. Whoever publishes last wins. Changes disappear. No audit trail per change set.
Do instead: Name workspaces by owner and change set (agency-consent-setup, dev-ecomm-events). GTM free accounts allow 3 workspaces. After publishing, delete the workspace to free a slot.
---
7. Missing GA4 Configuration Tag Reference
What: GA4 event tags created without setting a Configuration Tag reference, or referencing nothing (standalone).
Why it fails: GA4 event tags without a configuration parent do not send correctly. The measurement ID and shared settings from the config tag are not applied.
Do instead: Every GA4 event tag must have Configuration Tag set to the gaawc GA4 Config tag. Verify in tag settings.
---
8. dataLayer Version 1
What: Using Data Layer Variable configured as Version 1, or the legacy dataLayer push format from pre-2014 implementations.
Why it fails: Version 1 does not support nested object access. ecommerce.purchase.value returns undefined. Version 2 (default since 2014) handles nested objects correctly.
Do instead: Always configure Data Layer Variables as Version 2. Verify by opening the variable settings and confirming Version field is set to 2.
---
9. Not Clearing Ecommerce Data
What: Pushing a new ecommerce event without first clearing the previous ecommerce object.
Why it fails: GTM's data layer model is cumulative. If a view_item push set ecommerce.items to Product A, and then the add_to_cart push sets ecommerce.items to Product B, GTM may see both. Purchase events carry stale line items from earlier in the session.
Do instead:
window.dataLayer.push({ ecommerce: null }); // Clear
window.dataLayer.push({
event: 'purchase',
ecommerce: { ... }
});---
Data Layer Design Reference
Initialization
// Initialize before GTM snippet (or in GTM snippet config)
window.dataLayer = window.dataLayer || [];Push Convention
// Every meaningful push has an event key for Custom Event triggers
window.dataLayer.push({
event: 'event-name', // Required for Custom Event triggers
key: 'value',
nestedObject: {
property: 'value'
}
});GA4 Ecommerce Schema (v2 Flat Format)
// Clear first
window.dataLayer.push({ ecommerce: null });
// Purchase event
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: 'T12345',
value: 99.99,
tax: 8.50,
shipping: 5.99,
currency: 'USD',
coupon: 'SAVE10',
items: [
{
item_id: 'SKU_001',
item_name: 'Widget Pro',
item_brand: 'Acme',
item_category: 'Widgets',
item_category2: 'Pro Series',
price: 99.99,
quantity: 1,
coupon: 'SAVE10',
discount: 10.00
}
]
}
});Other recommended events use same ecommerce wrapper:
view_item— product detail pageadd_to_cart— cart additionremove_from_cart— cart removalbegin_checkout— checkout startadd_payment_info— payment stepadd_shipping_info— shipping step
Note: GA4 format uses ecommerce.value directly. Do NOT use ecommerce.purchase.value — that is the old Universal Analytics format.
Timing Rule
Push to dataLayer BEFORE the event you want to fire triggers. Server-rendered pages can push in <head>. SPAs must push before calling gtag or triggering the Custom Event.
---
Debugging Reference
Preview Mode
Activate by clicking Preview in GTM. Opens Tag Assistant in a new tab. Navigate to site URL in a paired browser window.
Left panel — Event Timeline:
- Lists every event in session (Page View, DOM Ready, Window Loaded, Custom Events)
- Click an event to see state at that moment
Right panel — Three tabs at each event:
- Tags — which tags fired (green check), which did not fire (grey), which fired with errors (red)
- Variables — all variable values at this event. Check here when debugging undefined values.
- Data Layer — the full data layer model state at this event. Shows cumulative pushes.
Consent tab — shows consent parameter state (granted/denied) at each event. Available in Tag Assistant.
Common Preview Mode Checks
| Problem | Where to Look | What to Check |
|---|---|---|
| Tag not firing | Tags tab → Not Fired section | Which trigger conditions failed? |
| Undefined parameter value | Variables tab | What is the DLV returning? Is the path correct? |
| Wrong trigger timing | Event Timeline | Did the event fire before or after the push? |
| Consent blocking | Consent tab | Are parameters set correctly? Is default firing first? |
| Duplicate tag fires | Tags tab → Fired section | Is tag using Once Per Page? Multiple triggers attached? |
Browser Console Inspection
// View entire data layer
console.log(JSON.stringify(window.dataLayer, null, 2));
// Filter to specific events
window.dataLayer.filter(d => d.event === 'purchase');
// Check current DL model state (GTM internal — only with Preview active)
// Not available without Preview modeNetwork Tab — GA4 Request Inspection
Filter requests by collect?v=2 or gtm/ to find GA4 hits.
Key parameters to verify:
| Parameter | Meaning |
|---|---|
en | Event name |
epn.transaction_id | Event parameter — transaction ID |
epn.value | Event parameter — value |
gcs | Consent state (see consent gcs table above) |
tid | Measurement ID (verify it's the right property) |
dh | Document hostname |
Diagnosing Common Scenarios
Scenario: Tag shows Fired but event missing in GA4 DebugView 1. Check Network tab — is there a collect request? 2. If yes: check tid matches correct property 3. Check epn. parameters — are values present or empty? 4. Check gcs — is analytics_storage granted?
Scenario: Tag shows Not Fired 1. Click event in timeline where tag should fire 2. Go to Tags tab → Not Fired section → click the tag 3. Read trigger conditions — which condition is false? 4. Go to Variables tab — what value does the condition variable return?
Scenario: Consent blocking data 1. Go to Consent tab in Tag Assistant 2. Check analytics_storage state at the Page View event 3. If denied: is the CMP update call firing? Did user consent? 4. Check network for gcs=G100 — both denied