skills/gtm-implementation/SKILL.md
Implements complete GTM tracking including dataLayer events in code and GTM configuration via API. Use when users need to "implement GTM tracking", "add dataLayer events", "create GTM variables and tags", "set up CTA tracking", "implement event tracking", or want to execute a tracking plan. Handles both code implementation (dataLayer.push) and GTM container configuration (variables/triggers/tags) automatically via API. Supports incremental updates and framework-specific patterns (React, Next.js, Vue, etc.).
npx skillsauth add aimonk2025/google-tag-manager-automation gtm-implementationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement complete GTM tracking by adding dataLayer events to your code AND creating GTM variables, triggers, and tags via API.
Transform analytics-ready DOM elements into fully tracked events with:
Check for gtm-context.md in the project root:
This file is created automatically by gtm-analytics-audit at the end of its first run.
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.
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"
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({...})
}
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}`)
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.
1. Preserve Existing Functionality
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:
React:
Vue:
4. Avoid Redundant Tracking
1. Naming Conventions
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
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.
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"
Core GTM API wrapper for creating variables/triggers/tags (extracted from gtm-setup-auto.js logic)
Variable creation logic with duplicate detection
Trigger creation logic for custom events
GA4 event tag creation with parameter mapping
Analyzes tracking plan and implements dataLayer.push() in code using Edit tool
Framework-specific dataLayer implementation patterns:
Manual GTM UI instructions as fallback if API fails
Common event naming patterns (object_action vs action_object)
Pre-built GTM config for SaaS product tracking:
Pre-built GTM config for e-commerce tracking:
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 outputPre-built GTM config for lead-gen tracking:
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.
development
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.
development
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".
development
Pipeline status check for GTM projects. Use when returning to a project mid-implementation, when unsure what step comes next, or to get a quick overview of what has been completed. Reads all GTM output files and shows which skills have run, current implementation coverage, and the recommended next step. No API calls, instant. Trigger on - "what step am I on", "gtm status", "where did I leave off", "what's been done", "check progress", "pipeline status".
development
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.