gtm-implementation

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.).

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "gtm-implementation" with this command: npx skills add aimonk2025/google-tag-manager-automation/aimonk2025-google-tag-manager-automation-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:

  1. Code Implementation: Add dataLayer.push() calls to React/Next.js/Vue components
  2. 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 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.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

gtm-analytics-audit

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

gtm-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

gtm-reporting

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

gtm-setup

No summary provided by upstream source.

Repository SourceNeeds Review