Files
our-claude-skills/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/datalayer_validation.md
Andrew Yim ec5e30c825 feat(gtm-manager): Rename to ourdigital-gtm-manager and add dataLayer injector
BREAKING CHANGE: Renamed from 13-gtm-audit to 13-ourdigital-gtm-manager

New Features:
- DataLayerInjector class for generating custom HTML tags
- Support for 20+ GA4 event types (ecommerce, forms, video, engagement)
- Subcommand structure: audit and inject modes
- Preset tag generation (ecommerce, engagement, all)
- DOM scraping option for dynamic value extraction
- Generate tags from audit report findings
- Korean payment method support (카카오페이, 네이버페이, 토스)

CLI Changes:
- `python gtm_manager.py audit --url ...` for auditing
- `python gtm_manager.py inject --preset ecommerce` for tag generation
- `python gtm_manager.py inject --event purchase --event add_to_cart`
- `python gtm_manager.py inject --from-audit report.json`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 20:55:42 +09:00

6.3 KiB

DataLayer Validation Reference

DataLayer Structure Basics

Proper Initialization

// Must appear BEFORE GTM script
<script>
  window.dataLayer = window.dataLayer || [];
</script>
<!-- GTM script here -->

Push Syntax

// Correct
dataLayer.push({ event: "page_view", page_title: "Home" });

// Wrong - direct assignment
dataLayer = [{ event: "page_view" }];  // ❌ Overwrites array

Validation Rules

Event Names

Rule Valid Invalid
Alphanumeric + underscore add_to_cart add-to-cart
Max 40 characters purchase (too long names)
Case sensitive addToCartaddtocart -
No spaces form_submit form submit
No special chars click_cta click@cta

Parameter Names

Rule Valid Invalid
Max 40 characters item_category (too long)
Alphanumeric + underscore user_id user-id
Cannot start with _ custom_param _private
Cannot start with number step_1 1_step

Data Types

Parameter Expected Type Example
value number 29.99 not "29.99"
currency string (ISO 4217) "USD", "KRW"
transaction_id string "T_12345"
quantity integer 2 not 2.0
price number 45000
items array [{...}, {...}]

Type Validation Code

function validateDataLayerPush(data) {
  const issues = [];
  
  // Check value is number
  if (data.ecommerce?.value !== undefined) {
    if (typeof data.ecommerce.value !== 'number') {
      issues.push(`value should be number, got ${typeof data.ecommerce.value}`);
    }
  }
  
  // Check currency format
  if (data.ecommerce?.currency) {
    if (!/^[A-Z]{3}$/.test(data.ecommerce.currency)) {
      issues.push(`currency should be 3-letter ISO code`);
    }
  }
  
  // Check items array
  if (data.ecommerce?.items) {
    if (!Array.isArray(data.ecommerce.items)) {
      issues.push(`items should be array`);
    } else {
      data.ecommerce.items.forEach((item, i) => {
        if (!item.item_id) issues.push(`items[${i}] missing item_id`);
        if (!item.item_name) issues.push(`items[${i}] missing item_name`);
        if (item.price && typeof item.price !== 'number') {
          issues.push(`items[${i}].price should be number`);
        }
        if (item.quantity && !Number.isInteger(item.quantity)) {
          issues.push(`items[${i}].quantity should be integer`);
        }
      });
    }
  }
  
  return issues;
}

E-commerce Object Clearing

Why Clear?

GA4 may merge previous ecommerce data with new events.

Correct Pattern

// Clear first
dataLayer.push({ ecommerce: null });

// Then push new event
dataLayer.push({
  event: "view_item",
  ecommerce: { ... }
});

Validation Check

function checkEcommerceClear(dataLayerArray) {
  let lastHadEcommerce = false;
  const issues = [];
  
  dataLayerArray.forEach((item, i) => {
    const hasEcommerce = 'ecommerce' in item;
    const isNull = item.ecommerce === null;
    
    if (hasEcommerce && !isNull && lastHadEcommerce) {
      issues.push({
        index: i,
        message: 'Missing ecommerce:null before this push'
      });
    }
    
    lastHadEcommerce = hasEcommerce && !isNull;
  });
  
  return issues;
}

Event Sequence Validation

Expected Sequences

E-commerce Purchase Flow:

view_item_list? → view_item → add_to_cart → view_cart → 
begin_checkout → add_shipping_info → add_payment_info → purchase

Form Submission:

form_start → form_submit → generate_lead?

User Authentication:

login | sign_up

Sequence Validator

function validateSequence(events, expectedOrder) {
  const eventNames = events
    .filter(e => e.event)
    .map(e => e.event);
  
  let lastIndex = -1;
  const issues = [];
  
  eventNames.forEach(event => {
    const index = expectedOrder.indexOf(event);
    if (index !== -1) {
      if (index < lastIndex) {
        issues.push(`${event} fired out of expected order`);
      }
      lastIndex = index;
    }
  });
  
  return issues;
}

Duplicate Event Detection

Common Duplicates

  • Multiple page_view on single page load
  • purchase firing on page refresh
  • Click events on bubbling elements

Detection Code

function findDuplicates(events) {
  const seen = {};
  const duplicates = [];
  
  events.forEach((event, i) => {
    if (!event.event) return;
    
    const key = JSON.stringify(event);
    if (seen[key]) {
      duplicates.push({
        event: event.event,
        firstIndex: seen[key],
        duplicateIndex: i
      });
    } else {
      seen[key] = i;
    }
  });
  
  return duplicates;
}

Real-time Monitoring Setup

Console Monitoring

// Paste in browser console to monitor pushes
(function() {
  const original = dataLayer.push;
  dataLayer.push = function() {
    console.group('📊 dataLayer.push');
    console.log('Data:', arguments[0]);
    console.log('Time:', new Date().toISOString());
    console.groupEnd();
    return original.apply(this, arguments);
  };
  console.log('✅ DataLayer monitoring active');
})();

Export DataLayer

// Copy full dataLayer to clipboard
copy(JSON.stringify(dataLayer, null, 2));

Validation Checklist

Structure

  • dataLayer initialized before GTM
  • Using push() not assignment
  • Event names follow conventions
  • Parameter names follow conventions

Data Types

  • value is number
  • currency is 3-letter code
  • quantity is integer
  • items is array
  • Required fields present

E-commerce

  • ecommerce:null before each push
  • items array has item_id and item_name
  • transaction_id is unique
  • Consistent currency across events

Sequence

  • Events fire in logical order
  • No duplicate events
  • Purchase fires only once

Debug Tools

GTM Preview Mode

  • Real-time event inspection
  • Variable value checking
  • Tag firing verification

GA4 DebugView

  • Live event stream
  • Parameter validation
  • User property tracking

Browser Console

// View current dataLayer
console.table(dataLayer);

// Filter by event
dataLayer.filter(d => d.event === 'purchase');