gtm-implementation
GTM Implementation - DataLayer + GTM API
Implement complete GTM tracking by adding dataLayer events to your code AND creating GTM variables, triggers, and tags via API.
Core Mission
Transform analytics-ready DOM elements into fully tracked events with:
- Code Implementation: Add dataLayer.push() calls to React/Next.js/Vue components
- GTM Configuration: Create variables, triggers, and tags via GTM API
Workflow
Phase 0: Load Business Context (if available)
Check for gtm-context.md in the project root:
- If found: read it silently and use the business context throughout this skill run
- If not found: proceed normally - ask context questions as usual during the workflow
This file is created automatically by gtm-analytics-audit at the end of its first run.
Phase 1: Context & Prerequisites
Step 1.1: Load Tracking Plan
Check for gtm-tracking-plan.json (from gtm-strategy skill):
- If exists → Load events and parameters
- If missing → Ask user which events to implement OR suggest running gtm-strategy first
Check for gtm-config.json and gtm-token.json (from gtm-setup skill):
- If missing → Suggest running gtm-setup skill first
- If exists → Verify API connection
Step 1.2: Detect Framework
Check package.json:
- React version
- Next.js version and router type (App Router vs Pages Router)
- Vue version
- TypeScript vs JavaScript
This determines:
- Component file extensions (.tsx vs .jsx vs .vue)
- Import patterns
- Syntax for className vs class
- 'use client' directive for Next.js App Router
Step 1.3: Scan for Target Elements
Using Glob and Grep, find elements to instrument:
For CTA tracking:
- Search for: class=".*js-cta.*" or id="cta_.*"
- Search for: <button, <Link components
For form tracking:
- Search for: class=".*js-form.*" or id="form_.*"
- Search for: <form tags with onSubmit handlers
For navigation tracking:
- Search for: class=".*js-nav.*" or id="nav_.*"
- Search for: <Link, <a tags
Match found elements against events in tracking plan.
Step 1.4: Resolve Active Workspace (Dynamic)
Before any GTM API operation, always resolve the current workspace dynamically:
// List all workspaces for this container
const workspacesResponse = await tagmanager.accounts.containers.workspaces.list({
parent: `accounts/${accountId}/containers/${containerId}`
})
const workspaces = workspacesResponse.data.workspace || []
if (workspaces.length === 0) {
throw new Error('No workspaces found in this GTM container. Create one in the GTM UI first.')
}
// Always use the first available workspace
const workspace = workspaces[0]
const workspaceId = workspace.workspaceId
console.log(`✓ Active workspace: "${workspace.name}" (ID: ${workspaceId})`)
Why: GTM deletes a workspace after manual submission in the GTM UI and creates a new one with a new ID. The stored workspaceId in gtm-config.json becomes stale. Never assume a specific workspace ID exists.
Phase 2: DataLayer Implementation (Code Changes)
For each event in tracking plan, implement dataLayer.push() in actual code files.
Step 2.1: CTA Click Implementation
Find target (example):
// File: app/page.tsx
<button
className="btn primary js-track js-cta js-click js-hero"
id="cta_hero_get_started"
>
Get Started
</button>
Implement tracking using Edit tool:
Before:
<button
className="btn primary js-track js-cta js-click js-hero"
id="cta_hero_get_started"
>
Get Started
</button>
After:
<button
className="btn primary js-track js-cta js-click js-hero"
id="cta_hero_get_started"
onClick={() => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'cta_click',
cta_location: 'hero',
cta_type: 'primary',
cta_text: 'Get Started',
cta_destination: '/signup'
})
}
}}
>
Get Started
</button>
Framework-Specific Considerations:
Next.js App Router (requires 'use client'):
'use client'
import { useRouter } from 'next/navigation'
export default function HeroCTA() {
const router = useRouter()
return (
<button
className="btn primary js-track js-cta js-click js-hero"
id="cta_hero_get_started"
onClick={() => {
// Track event
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'cta_click',
cta_location: 'hero',
cta_type: 'primary',
cta_text: 'Get Started',
cta_destination: '/signup'
})
}
// Navigate
router.push('/signup')
}}
>
Get Started
</button>
)
}
Step 2.2: Form Submit Implementation
Find target:
<form
className="contact-form js-track js-form js-submit js-hero"
id="form_hero_contact"
onSubmit={handleSubmit}
>
Implement tracking:
Before:
<form
className="contact-form js-track js-form js-submit js-hero"
id="form_hero_contact"
onSubmit={handleSubmit}
>
After:
<form
className="contact-form js-track js-form js-submit js-hero"
id="form_hero_contact"
onSubmit={(e) => {
// Track form submission
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'form_submit',
form_name: 'contact',
form_location: 'hero',
form_type: 'contact_request'
})
}
// Call original handler
handleSubmit(e)
}}
>
Step 2.3: Navigation Click Implementation
Find target (Next.js Link):
<Link href="/pricing">Pricing</Link>
Implement tracking:
Before:
<Link
href="/pricing"
className="nav-link js-track js-nav js-click js-header"
id="nav_header_pricing"
>
Pricing
</Link>
After:
<Link
href="/pricing"
className="nav-link js-track js-nav js-click js-header"
id="nav_header_pricing"
onClick={() => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'navigation_click',
nav_location: 'header',
nav_type: 'menu_link',
nav_text: 'Pricing',
nav_destination: '/pricing'
})
}
}}
>
Pricing
</Link>
Step 2.4: Parameter Extraction Logic
Extract parameters from DOM elements intelligently:
cta_location: From ID attribute
// id="cta_hero_get_started" → location: "hero"
const id = element.getAttribute('id') // "cta_hero_get_started"
const location = id.split('_')[1] // "hero"
cta_text: From button innerText
// <button>Get Started</button> → cta_text: "Get Started"
cta_text: 'Get Started'
cta_destination: From href or programmatic navigation
// <Link href="/signup"> → cta_destination: "/signup"
// onClick={() => router.push('/pricing')} → cta_destination: "/pricing"
cta_type: From CSS classes
// className includes "btn-primary" → type: "primary"
// className includes "btn-secondary" → type: "secondary"
Phase 3: GTM Container Configuration (API Calls)
Create variables, triggers, and tags via GTM API to process the dataLayer events.
Step 3.1: Create Data Layer Variables
For each parameter in each event, create a GTM variable:
Example: cta_click event needs 4 variables
// Variable 1: CTA Location
{
name: "DLV - CTA Location",
type: "v",
parameter: [
{ type: "TEMPLATE", key: "name", value: "cta_location" },
{ type: "INTEGER", key: "dataLayerVersion", value: "2" }
]
}
// Variable 2: CTA Type
{
name: "DLV - CTA Type",
type: "v",
parameter: [
{ type: "TEMPLATE", key: "name", value: "cta_type" },
{ type: "INTEGER", key: "dataLayerVersion", value: "2" }
]
}
// Variable 3: CTA Text
{
name: "DLV - CTA Text",
type: "v",
parameter: [
{ type: "TEMPLATE", key: "name", value: "cta_text" },
{ type: "INTEGER", key: "dataLayerVersion", value: "2" }
]
}
// Variable 4: CTA Destination
{
name: "DLV - CTA Destination",
type: "v",
parameter: [
{ type: "TEMPLATE", key: "name", value: "cta_destination" },
{ type: "INTEGER", key: "dataLayerVersion", value: "2" }
]
}
Use GTM API to create:
tagmanager.accounts.containers.workspaces.variables.create({
parent: `accounts/${accountId}/containers/${containerId}/workspaces/${workspaceId}`,
requestBody: variableConfig
})
Step 3.2: Create Custom Event Triggers
For each event, create a trigger that fires on that custom event:
Example: cta_click trigger
{
name: "CE - CTA Click",
type: "CUSTOM_EVENT",
customEventFilter: [
{
type: "EQUALS",
parameter: [
{ type: "TEMPLATE", key: "arg0", value: "{{_event}}" },
{ type: "TEMPLATE", key: "arg1", value: "cta_click" }
]
}
]
}
Use GTM API to create:
tagmanager.accounts.containers.workspaces.triggers.create({
parent: `accounts/${accountId}/containers/${containerId}/workspaces/${workspaceId}`,
requestBody: triggerConfig
})
Step 3.3: Create GA4 Event Tags
For each event, create a GA4 event tag that fires on the trigger:
Example: CTA Click tag
{
name: "GA4 - CTA Click",
type: "gaawe", // GA4 Event tag type
parameter: [
{
type: "TEMPLATE",
key: "eventName",
value: "cta_click"
},
{
type: "LIST",
key: "eventParameters",
list: [
{
type: "MAP",
map: [
{ type: "TEMPLATE", key: "name", value: "cta_location" },
{ type: "TEMPLATE", key: "value", value: "{{DLV - CTA Location}}" }
]
},
{
type: "MAP",
map: [
{ type: "TEMPLATE", key: "name", value: "cta_type" },
{ type: "TEMPLATE", key: "value", value: "{{DLV - CTA Type}}" }
]
},
{
type: "MAP",
map: [
{ type: "TEMPLATE", key: "name", value: "cta_text" },
{ type: "TEMPLATE", key: "value", value: "{{DLV - CTA Text}}" }
]
},
{
type: "MAP",
map: [
{ type: "TEMPLATE", key: "name", value: "cta_destination" },
{ type: "TEMPLATE", key: "value", value: "{{DLV - CTA Destination}}" }
]
}
]
},
{
type: "TAG_REFERENCE",
key: "measurementId",
value: "{{GA4 Configuration Tag}}" // Reference to GA4 config tag
}
],
firingTriggerId: ["{{CE - CTA Click trigger ID}}"]
}
Use GTM API to create:
tagmanager.accounts.containers.workspaces.tags.create({
parent: `accounts/${accountId}/containers/${containerId}/workspaces/${workspaceId}`,
requestBody: tagConfig
})
Step 3.4: Handle Incremental Updates
Check for existing variables/triggers/tags before creating:
// List existing variables
const existingVariables = await tagmanager.accounts.containers.workspaces.variables.list({
parent: `accounts/${accountId}/containers/${containerId}/workspaces/${workspaceId}`
})
// Check if "DLV - CTA Location" already exists
const exists = existingVariables.data.variable?.find(v => v.name === "DLV - CTA Location")
if (exists) {
// Update existing variable
tagmanager.accounts.containers.workspaces.variables.update({
path: exists.path,
requestBody: variableConfig
})
} else {
// Create new variable
tagmanager.accounts.containers.workspaces.variables.create({...})
}
Phase 4: Container Version Creation
After all variables/triggers/tags are created, create a new container version:
const version = await tagmanager.accounts.containers.workspaces.create_version({
path: `accounts/${accountId}/containers/${containerId}/workspaces/${workspaceId}`,
requestBody: {
name: `GTM Automation - ${new Date().toISOString()}`,
notes: `Automated implementation via gtm-implementation skill
Events implemented:
- cta_click (12 elements)
- form_submit (3 elements)
- navigation_click (8 elements)
Variables created: 12
Triggers created: 3
Tags created: 3`
}
})
console.log(`✓ Container version created: ${version.data.containerVersionId}`)
console.log(`→ Preview in GTM: ${version.data.containerVersion.tagManagerUrl}`)
Phase 5: Summary Report
Generate comprehensive implementation summary:
=== GTM Implementation Complete ===
--- Code Changes ---
Files modified: 8
app/page.tsx:
✓ Added CTA click tracking (3 buttons)
✓ Added form submit tracking (1 form)
components/Navbar.tsx:
✓ Added navigation click tracking (5 links)
components/Footer.tsx:
✓ Added navigation click tracking (3 links)
✓ Added form submit tracking (1 newsletter form)
... (4 more files)
--- DataLayer Events Implemented ---
✓ cta_click (12 elements tracked)
✓ form_submit (3 elements tracked)
✓ navigation_click (8 elements tracked)
Total events: 23
--- GTM Container Configuration ---
Account: 1234567890
Container: GTM-ABC1234
Workspace: [dynamically resolved name] (ID: [resolved ID])
Created via API:
✓ 12 Data Layer Variables
✓ 3 Custom Event Triggers
✓ 3 GA4 Event Tags
Container version created: 42
Preview URL: https://tagmanager.google.com/#/versions/accounts/123/containers/456/versions/42
--- Next Steps ---
1. Test tracking in GTM Preview mode
→ Invoke gtm-testing skill for guided testing
2. Review container version in GTM:
→ Open GTM → Versions → Version 42
→ Check variables, triggers, tags
3. Publish when ready:
→ GTM → Submit → Publish
Ready to test tracking? Invoke gtm-testing skill.
Important Guidelines
Code Implementation Best Practices
1. Preserve Existing Functionality
- NEVER remove existing onClick handlers
- Call original handlers AFTER tracking
- Use wrapper functions when needed
2. Type Safety (TypeScript)
// Add proper typing
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
// Track
window.dataLayer?.push({...})
// Original handler
originalHandler(e)
}}
3. Framework-Specific Patterns
Next.js App Router:
- Add 'use client' directive to files with onClick
- Use useRouter from 'next/navigation'
React:
- Use functional components with hooks
- Maintain existing state management
Vue:
- Use @click syntax
- Maintain reactivity
4. Avoid Redundant Tracking
- If element already has dataLayer.push, UPDATE it (don't duplicate)
- Check for existing tracking code before adding new
GTM API Best Practices
1. Naming Conventions
- Variables: "DLV - {Parameter Name}" (DLV = Data Layer Variable)
- Triggers: "CE - {Event Name}" (CE = Custom Event)
- Tags: "GA4 - {Event Name}"
2. Error Handling
try {
await tagmanager.accounts.containers.workspaces.variables.create({...})
} catch (error) {
if (error.code === 409) {
// Variable already exists - update instead
} else if (error.code === 403) {
// Permission denied
} else {
throw error
}
}
3. Batch Operations
- Create all variables first
- Then create triggers (which may reference variables)
- Finally create tags (which reference both)
4. Always Resolve Workspace Dynamically Never use a hardcoded or cached workspaceId. Before any operation, call:
const workspaces = await tagmanager.accounts.containers.workspaces.list({
parent: `accounts/${accountId}/containers/${containerId}`
})
const workspaceId = workspaces.data.workspace[0].workspaceId
GTM deletes and recreates workspaces on each publish, so stored IDs become stale.
Parameter Extraction Intelligence
Be smart about extracting parameters:
From ID attributes:
// id="cta_hero_get_started"
const [category, location, ...action] = id.split('_')
// category: "cta", location: "hero", action: ["get", "started"]
From class names:
// className="btn btn-primary"
const isPrimary = className.includes('primary')
const type = isPrimary ? 'primary' : 'secondary'
From content:
// <button>Get Started</button>
const text = element.innerText // "Get Started"
From href/destination:
// <Link href="/pricing">
const destination = href // "/pricing"
// onClick={() => router.push('/signup')}
// Parse from handler → destination: "/signup"
Scripts
scripts/gtm-api-client.js
Core GTM API wrapper for creating variables/triggers/tags (extracted from gtm-setup-auto.js logic)
scripts/create-variables.js
Variable creation logic with duplicate detection
scripts/create-triggers.js
Trigger creation logic for custom events
scripts/create-tags.js
GA4 event tag creation with parameter mapping
scripts/implement-datalayer.js
Analyzes tracking plan and implements dataLayer.push() in code using Edit tool
References
references/datalayer-patterns.md
Framework-specific dataLayer implementation patterns:
- Next.js App Router ('use client' directive)
- Next.js Pages Router
- React (hooks, class components)
- Vue (composition API, options API)
- Vanilla JavaScript
references/gtm-ui-guide.md
Manual GTM UI instructions as fallback if API fails
references/event-taxonomies.md
Common event naming patterns (object_action vs action_object)
Assets
assets/templates/saas.json
Pre-built GTM config for SaaS product tracking:
- trial_start, feature_usage, upgrade_click
- account_created, plan_selected
assets/templates/ecommerce.json
Pre-built GTM config for e-commerce tracking:
- product_view, add_to_cart, checkout_start
- purchase_complete, product_search
Supporting Files
template.md- Framework-specific dataLayer push code patterns and GTM API payload templatesexamples/sample.md- Example implementation output showing before/after code changes and console output
assets/templates/lead-generation.json
Pre-built GTM config for lead-gen tracking:
- form_start, form_submit, content_download
- demo_request, newsletter_signup
Execution Checklist
- Tracking plan loaded or events specified
- GTM API credentials validated
- Framework detected
- Target elements identified
- DataLayer events implemented in code
- Variables created via GTM API
- Triggers created via GTM API
- Tags created via GTM API
- Container version created
- Implementation summary generated
- Testing instructions provided
Common Questions
Q: What if I don't have a tracking plan? A: Run gtm-strategy skill first to create one, OR specify events manually and we'll implement them.
Q: Can I implement tracking without using the GTM API? A: Yes. We'll implement dataLayer events in code and provide manual GTM UI instructions.
Q: Will this overwrite existing GTM configuration? A: No. We check for existing variables/triggers/tags and update them. New configs are added, not replaced.
Q: Can I implement tracking for only specific events? A: Yes. Specify which events to implement (e.g., "only implement cta_click tracking").
Q: What if my framework isn't supported? A: The dataLayer.push pattern works in any JavaScript environment. We'll provide vanilla JS implementation.
Q: My workspace ID changed after publishing in GTM. Will the skill break? A: No. The skill always lists available workspaces via the API and uses the first one found, regardless of its ID. You never need to update gtm-config.json after publishing.
More from aimonk2025/google-tag-manager-automation
gtm-analytics-audit
Comprehensive analytics audit of website codebase to identify trackable elements and assess analytics readiness. Use when users want to "audit my analytics", "scan for trackable elements", "find what I can track", "analyze my website for tracking opportunities", or before implementing GTM tracking. Scans HTML/JSX/TSX/Vue for all clickable elements (buttons, links, forms, etc.), identifies existing tracking code, evaluates DOM structure for analytics, and provides recommendations. Acts as senior frontend engineer with GA4 expertise.
79gtm-testing
Comprehensive GTM tracking testing and validation including automated Playwright headless testing, browser console testing, GTM Preview mode validation, and GA4 DebugView verification. Use when users need to "test GTM tracking", "validate dataLayer events", "debug GTM", "check if tracking works", "automated tracking tests", "run tracking tests without opening browser", or troubleshoot tracking issues. Prioritises automated testing over manual when possible.
65gtm-setup
Automates Google Tag Manager API setup including googleapis installation, OAuth credential creation, token management, and prerequisites validation. Use when users need to "set up GTM API", "configure GTM API access", "get GTM OAuth credentials", "install googleapis", or encounter authentication errors. Handles complete technical setup from dependency installation through API connection verification.
61gtm-reporting
Generates GTM implementation documentation, reporting impact analysis, GA4 report configurations, and stakeholder summaries. Use when users need to "document GTM implementation", "what reports can I build", "create event schema docs", "generate stakeholder summary", "analyze reporting impact", or want to understand business value of tracking data. Creates technical documentation, suggests GA4 explorations, defines remarketing audiences, and translates technical events into business insights.
60gtm-strategy
Strategic GTM tracking planning with product manager expertise. Use when users need to plan tracking strategy, define what metrics to measure, understand business impact of tracking, create tracking specifications, or need guidance on "what should I track?" questions. Asks discovery questions about business goals, maps objectives to events, defines event taxonomy, and creates structured tracking plans. Trigger on - "plan GTM tracking", "what should I track", "create tracking plan", "define measurement strategy", "GTM strategy".
54gtm-dom-standardization
Standardizes all click-related IDs and CSS classes across website for clean analytics tracking. Use when users want to "standardize analytics classes", "clean up tracking IDs", "prepare DOM for GTM", "fix analytics naming", or "make tracking consistent". Scans entire codebase (HTML/JSX/TSX/Vue) and applies consistent naming convention - IDs as "cta_{location}_{action}" and classes as "js-track js-{category} js-{action} js-{location}". Acts as senior frontend engineer ensuring scalable GA4/GTM implementation.
47