gtm-implementation.md
396 lines · skills/analytics/references/gtm-implementation.md at 88c79f6
Google Tag Manager Implementation Reference
Detailed guide for implementing tracking via Google Tag Manager.
Raw lines 1 to 4Contents
- Container Structure (tags, triggers, variables)
- Naming Conventions
- Data Layer Patterns
- Common Tag Configurations (GA4 configuration tag, GA4 event tag, Facebook pixel)
- Preview and Debug
- Workspaces and Versioning
- Consent Management
- Advanced Patterns (tag sequencing, exception handling, custom JavaScript variables)
Container Structure
Raw lines 15 to 16Tags
Tags are code snippets that execute when triggered.
Common tag types:
- GA4 Configuration (base setup)
- GA4 Event (custom events)
- Google Ads Conversion
- Facebook Pixel
- LinkedIn Insight Tag
- Custom HTML (for other pixels)
Triggers
Triggers define when tags fire.
Built-in triggers:
- Page View: All Pages, DOM Ready, Window Loaded
- Click: All Elements, Just Links
- Form Submission
- Scroll Depth
- Timer
- Element Visibility
Custom triggers:
- Custom Event (from dataLayer)
- Trigger Groups (multiple conditions)
Variables
Variables capture dynamic values.
Built-in (enable as needed):
- Click Text, Click URL, Click ID, Click Classes
- Page Path, Page URL, Page Hostname
- Referrer
- Form Element, Form ID
User-defined:
- Data Layer variables
- JavaScript variables
- Lookup tables
- RegEx tables
- Constants
Raw lines 45 to 63
Naming Conventions
Raw lines 64 to 65Recommended Format
[Type] - [Description] - [Detail]
Tags:
GA4 - Event - Signup Completed
GA4 - Config - Base Configuration
FB - Pixel - Page View
HTML - LiveChat Widget
Triggers:
Click - CTA Button
Submit - Contact Form
View - Pricing Page
Custom - signup_completed
Variables:
DL - user_id
JS - Current Timestamp
LT - Campaign Source MapRaw lines 66 to 90
Data Layer Patterns
Raw lines 91 to 92Basic Structure
// Initialize (in <head> before GTM)
window.dataLayer = window.dataLayer || [];
// Push event
dataLayer.push({
'event': 'event_name',
'property1': 'value1',
'property2': 'value2'
});Raw lines 93 to 106Page Load Data
// Set on page load (before GTM container)
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'pageType': 'product',
'contentGroup': 'products',
'user': {
'loggedIn': true,
'userId': '12345',
'userType': 'premium'
}
});Raw lines 107 to 122Form Submission
document.querySelector('#contact-form').addEventListener('submit', function() {
dataLayer.push({
'event': 'form_submitted',
'formName': 'contact',
'formLocation': 'footer'
});
});Raw lines 123 to 134Button Click
document.querySelector('.cta-button').addEventListener('click', function() {
dataLayer.push({
'event': 'cta_clicked',
'ctaText': this.innerText,
'ctaLocation': 'hero'
});
});Raw lines 135 to 146E-commerce Events
// Product view
dataLayer.push({ ecommerce: null }); // Clear previous
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'items': [{
'item_id': 'SKU123',
'item_name': 'Product Name',
'price': 99.99,
'item_category': 'Category',
'quantity': 1
}]
}
});
// Add to cart
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'add_to_cart',
'ecommerce': {
'items': [{
'item_id': 'SKU123',
'item_name': 'Product Name',
'price': 99.99,
'quantity': 1
}]
}
});
// Purchase
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': 'T12345',
'value': 99.99,
'currency': 'USD',
'tax': 5.00,
'shipping': 10.00,
'items': [{
'item_id': 'SKU123',
'item_name': 'Product Name',
'price': 99.99,
'quantity': 1
}]
}
});Raw lines 147 to 200
Common Tag Configurations
Raw lines 201 to 202GA4 Configuration Tag
Tag Type: Google Analytics: GA4 Configuration
Settings:
- Measurement ID: G-XXXXXXXX
- Send page view: Checked (for pageviews)
- User Properties: Add any user-level dimensions
Trigger: All Pages
Raw lines 203 to 213GA4 Event Tag
Tag Type: Google Analytics: GA4 Event
Settings:
- Configuration Tag: Select your config tag
- Event Name: {{DL - event_name}} or hardcode
- Event Parameters: Add parameters from dataLayer
Trigger: Custom Event with event name match
Raw lines 214 to 224Facebook Pixel - Base
Tag Type: Custom HTML
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');
</script>Trigger: All Pages
Raw lines 225 to 245Facebook Pixel - Event
Tag Type: Custom HTML
<script>
fbq('track', 'Lead', {
content_name: '{{DL - form_name}}'
});
</script>Trigger: Custom Event - form_submitted
Raw lines 246 to 261
Preview and Debug
Raw lines 262 to 263Preview Mode
- Click "Preview" in GTM
- Enter site URL
- GTM debug panel opens at bottom
What to check:
- Tags fired on this event
- Tags not fired (and why)
- Variables and their values
- Data layer contents
Debug Tips
Tag not firing:
- Check trigger conditions
- Verify data layer push
- Check tag sequencing
Wrong variable value:
- Check data layer structure
- Verify variable path (nested objects)
- Check timing (data may not exist yet)
Multiple firings:
- Check trigger uniqueness
- Look for duplicate tags
- Check tag firing options
Raw lines 276 to 294
Workspaces and Versioning
Raw lines 295 to 296Workspaces
Use workspaces for team collaboration:
- Default workspace for production
- Separate workspaces for large changes
- Merge when ready
Version Management
Best practices:
- Name every version descriptively
- Add notes explaining changes
- Review changes before publish
- Keep production version noted
Version notes example:
v15: Added purchase conversion tracking
- New tag: GA4 - Event - Purchase
- New trigger: Custom Event - purchase
- New variables: DL - transaction_id, DL - value
- Tested: Chrome, Safari, MobileRaw lines 304 to 322
Consent Management
Raw lines 323 to 324Consent Mode Integration
// Default state (before consent)
gtag('consent', 'default', {
'analytics_storage': 'denied',
'ad_storage': 'denied'
});
// Update on consent
function grantConsent() {
gtag('consent', 'update', {
'analytics_storage': 'granted',
'ad_storage': 'granted'
});
}Raw lines 325 to 342GTM Consent Overview
- Enable Consent Overview in Admin
- Configure consent for each tag
- Tags respect consent state automatically
Raw lines 343 to 350
Advanced Patterns
Raw lines 351 to 352Tag Sequencing
Setup tags to fire in order: Tag Configuration > Advanced Settings > Tag Sequencing
Use cases:
- Config tag before event tags
- Pixel initialization before tracking
- Cleanup after conversion
Exception Handling
Trigger exceptions - Prevent tag from firing:
- Exclude certain pages
- Exclude internal traffic
- Exclude during testing
Custom JavaScript Variables
// Get URL parameter
function() {
var params = new URLSearchParams(window.location.search);
return params.get('campaign') || '(not set)';
}
// Get cookie value
function() {
var match = document.cookie.match('(^|;) ?user_id=([^;]*)(;|$)');
return match ? match[2] : null;
}
// Read a machine-readable amount, not the localized display text.
// Example: <span class="product-price" data-price="1234.56">$1,234.56</span>
function() {
var el = document.querySelector('.product-price');
var raw = el ? el.getAttribute('data-price') : null;
if (raw === null || !/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(raw)) return null;
var amount = Number(raw);
return isFinite(amount) ? amount : null;
}For monetary tracking, prefer the numeric amount and currency from the application data layer. If a DOM variable is needed, have the application render data-price as an unformatted, non-negative decimal in major currency units; pass its currency separately. Do not infer the amount or currency from presentation text: parseFloat turns $1,234.56 into 1 after removing $, and localized separators have different meanings. A missing or invalid amount returns null, so skip that monetary event or diagnose it before sending; reserve 0 for an explicitly free item.