# DataLayer Validation Reference ## DataLayer Structure Basics ### Proper Initialization ```javascript // Must appear BEFORE GTM script ``` ### Push Syntax ```javascript // 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 | `addToCart` ≠ `addtocart` | - | | 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 ```javascript 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 ```javascript // Clear first dataLayer.push({ ecommerce: null }); // Then push new event dataLayer.push({ event: "view_item", ecommerce: { ... } }); ``` ### Validation Check ```javascript 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 ```javascript 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 ```javascript 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 ```javascript // 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 ```javascript // 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 ```javascript // View current dataLayer console.table(dataLayer); // Filter by event dataLayer.filter(d => d.event === 'purchase'); ```