- 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
- // → cta_text: "Get Started"
- cta_text
- :
- 'Get Started'
- cta_destination
-
- From href or programmatic navigation
- // → 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 : // const text = element . innerText // "Get Started" From href/destination : // 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 templates examples/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.
gtm-implementation
安装
npx skills add https://github.com/aimonk2025/google-tag-manager-automation --skill gtm-implementation