feat(gtm-audit): Add GTM audit skill with Notion integration
- Automated GTM container detection and validation - DataLayer event validation against GA4 specs - Form tracking analysis and interaction simulation - E-commerce checkout flow analysis - Multi-platform support (GA4, Meta, LinkedIn, Google Ads, Kakao, Naver) - Notion database export with detailed reporting - Korean market considerations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
151
ourdigital-custom-skills/13-gtm-audit/CLAUDE.md
Normal file
151
ourdigital-custom-skills/13-gtm-audit/CLAUDE.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# GTM Audit Tool
|
||||||
|
|
||||||
|
Automated Google Tag Manager audit toolkit using Playwright browser automation.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This tool audits GTM container installations, validates dataLayer events, tests form tracking, simulates e-commerce checkout flows, and generates comprehensive reports.
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
pip install playwright
|
||||||
|
playwright install chromium
|
||||||
|
|
||||||
|
# Run full audit
|
||||||
|
python gtm_audit.py --url "https://example.com" --journey full
|
||||||
|
|
||||||
|
# Form tracking audit
|
||||||
|
python gtm_audit.py --url "https://example.com/contact" --journey form
|
||||||
|
|
||||||
|
# E-commerce checkout flow
|
||||||
|
python gtm_audit.py --url "https://example.com/cart" --journey checkout
|
||||||
|
|
||||||
|
# DataLayer deep inspection
|
||||||
|
python gtm_audit.py --url "https://example.com" --journey datalayer
|
||||||
|
|
||||||
|
# With specific container validation
|
||||||
|
python gtm_audit.py --url "https://example.com" --container "GTM-XXXXXX"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Journey Types
|
||||||
|
|
||||||
|
| Journey | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `pageview` | Basic page load + scroll simulation |
|
||||||
|
| `scroll` | Scroll depth trigger testing (25%, 50%, 75%, 90%) |
|
||||||
|
| `form` | Form discovery, field analysis, interaction simulation |
|
||||||
|
| `checkout` | E-commerce flow: cart → checkout → shipping → payment → purchase |
|
||||||
|
| `datalayer` | Deep dataLayer validation and event sequence analysis |
|
||||||
|
| `full` | All of the above combined |
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Generates `gtm_audit_report.json` with:
|
||||||
|
- Container status (installed, position, duplicates)
|
||||||
|
- DataLayer analysis (events, validation issues, sequence errors)
|
||||||
|
- Form analysis (forms found, tracking readiness, missing events)
|
||||||
|
- Checkout analysis (elements detected, flow issues)
|
||||||
|
- Network requests (GA4, Meta, LinkedIn, etc.)
|
||||||
|
- Recommendations and checklist
|
||||||
|
|
||||||
|
## Notion Integration
|
||||||
|
|
||||||
|
Export audit results directly to Notion database for tracking and collaboration.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Export to default Notion database (OurDigital GTM Audit Log)
|
||||||
|
python gtm_audit.py --url "https://example.com" --notion
|
||||||
|
|
||||||
|
# Export with detailed content (issues, recommendations, checklist)
|
||||||
|
python gtm_audit.py --url "https://example.com" --notion --notion-detailed
|
||||||
|
|
||||||
|
# Export to custom Notion database
|
||||||
|
python gtm_audit.py --url "https://example.com" --notion --notion-database "your-database-id"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notion Database Schema
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| Site | Title | Domain name of audited site |
|
||||||
|
| Audit ID | Text | Unique identifier (GTM-domain-date-hash) |
|
||||||
|
| URL | URL | Full audited URL |
|
||||||
|
| Audit Date | Date | When audit was performed |
|
||||||
|
| Journey Type | Select | Audit journey type |
|
||||||
|
| GTM Status | Select | Installed / Not Found / Multiple Containers |
|
||||||
|
| Container IDs | Text | GTM container IDs found |
|
||||||
|
| Tags Fired | Multi-select | GA4, Google Ads, Meta Pixel, etc. |
|
||||||
|
| Issues Count | Number | Total issues found |
|
||||||
|
| Critical Issues | Number | Critical/error severity issues |
|
||||||
|
| Audit Status | Select | Pass / Warning / Fail |
|
||||||
|
| Summary | Text | Quick summary of findings |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Set `NOTION_TOKEN` or `NOTION_API_KEY` for Notion API authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NOTION_TOKEN="secret_xxxxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default Database
|
||||||
|
|
||||||
|
Default Notion database: [OurDigital GTM Audit Log](https://www.notion.so/2cf581e58a1e8163997fccb387156a20)
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `gtm_audit.py` - Main audit script
|
||||||
|
- `docs/ga4_events.md` - GA4 event specifications
|
||||||
|
- `docs/ecommerce_schema.md` - E-commerce dataLayer structures
|
||||||
|
- `docs/form_tracking.md` - Form event patterns
|
||||||
|
- `docs/checkout_flow.md` - Checkout funnel sequence
|
||||||
|
- `docs/datalayer_validation.md` - Validation rules
|
||||||
|
- `docs/common_issues.md` - Frequent problems and fixes
|
||||||
|
|
||||||
|
## Coding Guidelines
|
||||||
|
|
||||||
|
When modifying this tool:
|
||||||
|
|
||||||
|
1. **Tag Destinations**: Add new platforms to `TAG_DESTINATIONS` dict
|
||||||
|
2. **Event Validation**: Add requirements to `GA4_EVENT_REQUIREMENTS` dict
|
||||||
|
3. **Form Selectors**: Extend `FormAnalyzer.discover_forms()` for custom forms
|
||||||
|
4. **Checkout Elements**: Add selectors to `CheckoutFlowAnalyzer.detect_checkout_elements()`
|
||||||
|
|
||||||
|
## Korean Market Considerations
|
||||||
|
|
||||||
|
- Support Korean payment methods (카카오페이, 네이버페이, 토스)
|
||||||
|
- Handle KRW currency (no decimals)
|
||||||
|
- Include Kakao Pixel and Naver Analytics patterns
|
||||||
|
- Korean button text patterns (장바구니, 결제하기, 주문하기)
|
||||||
|
|
||||||
|
## Testing a New Site
|
||||||
|
|
||||||
|
1. Run with `--journey full` first to get complete picture
|
||||||
|
2. Check `gtm_audit_report.json` for issues
|
||||||
|
3. Focus on specific areas with targeted journey types
|
||||||
|
4. Use `--container GTM-XXXXXX` to validate specific container
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Add support for new tag platform
|
||||||
|
```python
|
||||||
|
# In TAG_DESTINATIONS dict
|
||||||
|
"NewPlatform": [
|
||||||
|
r"tracking\.newplatform\.com",
|
||||||
|
r"pixel\.newplatform\.com",
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add custom form field detection
|
||||||
|
```python
|
||||||
|
# In FormAnalyzer.discover_forms()
|
||||||
|
# Add new field types or selectors
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extend checkout flow for specific platform
|
||||||
|
```python
|
||||||
|
# In CheckoutFlowAnalyzer.detect_checkout_elements()
|
||||||
|
# Add platform-specific selectors
|
||||||
|
```
|
||||||
115
ourdigital-custom-skills/13-gtm-audit/README.md
Normal file
115
ourdigital-custom-skills/13-gtm-audit/README.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# GTM Audit Tool
|
||||||
|
|
||||||
|
Automated Google Tag Manager audit toolkit powered by Playwright.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Container Detection**: Verify GTM installation, position, and duplicates
|
||||||
|
- **DataLayer Validation**: Event structure, types, sequence checking
|
||||||
|
- **Form Tracking**: Form discovery, field analysis, event verification
|
||||||
|
- **E-commerce Checkout**: Full funnel flow simulation and validation
|
||||||
|
- **Multi-Platform**: GA4, Meta Pixel, LinkedIn, Google Ads, Kakao, Naver
|
||||||
|
- **Notion Integration**: Export audit results to Notion database for tracking
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or download
|
||||||
|
cd gtm-audit-claude-code
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Install Playwright browsers
|
||||||
|
playwright install chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full audit
|
||||||
|
python gtm_audit.py --url "https://yoursite.com" --journey full
|
||||||
|
|
||||||
|
# Specific container validation
|
||||||
|
python gtm_audit.py --url "https://yoursite.com" --container "GTM-XXXXXX"
|
||||||
|
|
||||||
|
# Form tracking only
|
||||||
|
python gtm_audit.py --url "https://yoursite.com/contact" --journey form
|
||||||
|
|
||||||
|
# E-commerce checkout
|
||||||
|
python gtm_audit.py --url "https://yoursite.com/cart" --journey checkout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description | Default |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `--url` | Target URL (required) | - |
|
||||||
|
| `--container` | Expected GTM container ID | None |
|
||||||
|
| `--journey` | Audit type: pageview, scroll, form, checkout, datalayer, full | full |
|
||||||
|
| `--output` | Output file path | gtm_audit_report.json |
|
||||||
|
| `--timeout` | Page load timeout (ms) | 30000 |
|
||||||
|
| `--headless` | Run browser headless | True |
|
||||||
|
| `--notion` | Export results to Notion database | False |
|
||||||
|
| `--notion-database` | Custom Notion database ID | OurDigital GTM Audit Log |
|
||||||
|
| `--notion-detailed` | Add detailed content to Notion page | False |
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Generates JSON report with:
|
||||||
|
- Container status
|
||||||
|
- DataLayer events and validation issues
|
||||||
|
- Form analysis and tracking readiness
|
||||||
|
- Checkout flow analysis
|
||||||
|
- Network requests by destination
|
||||||
|
- Recommendations and checklist
|
||||||
|
|
||||||
|
## Notion Integration
|
||||||
|
|
||||||
|
Export audit results directly to Notion for team collaboration and historical tracking.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set Notion API token
|
||||||
|
export NOTION_TOKEN="secret_xxxxx"
|
||||||
|
|
||||||
|
# Export to Notion (default database)
|
||||||
|
python gtm_audit.py --url "https://yoursite.com" --notion
|
||||||
|
|
||||||
|
# Export with detailed content (issues, recommendations, checklist)
|
||||||
|
python gtm_audit.py --url "https://yoursite.com" --notion --notion-detailed
|
||||||
|
```
|
||||||
|
|
||||||
|
The audit creates a new row in the Notion database with:
|
||||||
|
- Site name and URL
|
||||||
|
- Audit date and unique ID
|
||||||
|
- GTM status and container IDs
|
||||||
|
- Tags fired (GA4, Meta, etc.)
|
||||||
|
- Issue counts and overall status
|
||||||
|
- Summary of findings
|
||||||
|
|
||||||
|
## Using with Claude Code
|
||||||
|
|
||||||
|
This project includes a `CLAUDE.md` file optimized for use with Claude Code.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In your terminal
|
||||||
|
claude
|
||||||
|
|
||||||
|
# Then ask Claude to run audits
|
||||||
|
> Run a GTM audit on https://example.com
|
||||||
|
> Check the form tracking on https://example.com/contact
|
||||||
|
> Analyze the checkout flow issues in the latest report
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See `docs/` folder for:
|
||||||
|
- GA4 event specifications
|
||||||
|
- E-commerce dataLayer schemas
|
||||||
|
- Form tracking patterns
|
||||||
|
- Checkout flow sequences
|
||||||
|
- Common issues and fixes
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
237
ourdigital-custom-skills/13-gtm-audit/docs/checkout_flow.md
Normal file
237
ourdigital-custom-skills/13-gtm-audit/docs/checkout_flow.md
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# E-commerce Checkout Flow Reference
|
||||||
|
|
||||||
|
## Complete Checkout Event Sequence
|
||||||
|
|
||||||
|
```
|
||||||
|
view_cart → begin_checkout → add_shipping_info → add_payment_info → purchase
|
||||||
|
```
|
||||||
|
|
||||||
|
Each step must fire in order with consistent item data.
|
||||||
|
|
||||||
|
## Event Details
|
||||||
|
|
||||||
|
### 1. view_cart
|
||||||
|
When user views cart page.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "view_cart",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "KRW",
|
||||||
|
value: 125000,
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Blue T-Shirt",
|
||||||
|
price: 45000,
|
||||||
|
quantity: 2,
|
||||||
|
item_brand: "Brand",
|
||||||
|
item_category: "Apparel"
|
||||||
|
}, {
|
||||||
|
item_id: "SKU_002",
|
||||||
|
item_name: "Black Jeans",
|
||||||
|
price: 35000,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. begin_checkout
|
||||||
|
When user initiates checkout process.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "begin_checkout",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "KRW",
|
||||||
|
value: 125000,
|
||||||
|
coupon: "SUMMER10",
|
||||||
|
items: [/* same items as view_cart */]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. add_shipping_info
|
||||||
|
When user completes shipping step.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "add_shipping_info",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "KRW",
|
||||||
|
value: 125000,
|
||||||
|
coupon: "SUMMER10",
|
||||||
|
shipping_tier: "Express", // Required
|
||||||
|
items: [/* same items */]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**shipping_tier values:**
|
||||||
|
- "Standard" / "일반배송"
|
||||||
|
- "Express" / "익일배송"
|
||||||
|
- "Same Day" / "당일배송"
|
||||||
|
- "Free" / "무료배송"
|
||||||
|
- "Store Pickup" / "매장픽업"
|
||||||
|
|
||||||
|
### 4. add_payment_info
|
||||||
|
When user enters payment details.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "add_payment_info",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "KRW",
|
||||||
|
value: 125000,
|
||||||
|
coupon: "SUMMER10",
|
||||||
|
payment_type: "Credit Card", // Required
|
||||||
|
items: [/* same items */]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**payment_type values:**
|
||||||
|
- "Credit Card" / "신용카드"
|
||||||
|
- "Debit Card" / "체크카드"
|
||||||
|
- "Bank Transfer" / "계좌이체"
|
||||||
|
- "Virtual Account" / "가상계좌"
|
||||||
|
- "Mobile Payment" / "휴대폰결제"
|
||||||
|
- "Kakao Pay" / "카카오페이"
|
||||||
|
- "Naver Pay" / "네이버페이"
|
||||||
|
- "Toss" / "토스"
|
||||||
|
- "PayPal"
|
||||||
|
|
||||||
|
### 5. purchase
|
||||||
|
When transaction completes successfully.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "purchase",
|
||||||
|
ecommerce: {
|
||||||
|
transaction_id: "T_20250115_001234", // Required, unique
|
||||||
|
value: 130500, // Required (total)
|
||||||
|
tax: 11863,
|
||||||
|
shipping: 5000,
|
||||||
|
currency: "KRW", // Required
|
||||||
|
coupon: "SUMMER10",
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Blue T-Shirt",
|
||||||
|
affiliation: "Online Store",
|
||||||
|
coupon: "SUMMER10",
|
||||||
|
discount: 4500,
|
||||||
|
price: 45000,
|
||||||
|
quantity: 2
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Funnel Drop-off Analysis
|
||||||
|
|
||||||
|
### Tracking Drop-offs
|
||||||
|
Monitor completion rate at each step:
|
||||||
|
|
||||||
|
| Step | Event | Drop-off Indicator |
|
||||||
|
|------|-------|-------------------|
|
||||||
|
| Cart | view_cart | User leaves cart page |
|
||||||
|
| Checkout Start | begin_checkout | User doesn't proceed |
|
||||||
|
| Shipping | add_shipping_info | Address form abandoned |
|
||||||
|
| Payment | add_payment_info | Payment not completed |
|
||||||
|
| Complete | purchase | Transaction failed |
|
||||||
|
|
||||||
|
### Implementing Drop-off Tracking
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Track checkout step viewed but not completed
|
||||||
|
let checkoutStep = 0;
|
||||||
|
|
||||||
|
function trackCheckoutProgress(step) {
|
||||||
|
if (step > checkoutStep) {
|
||||||
|
checkoutStep = step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
if (checkoutStep > 0 && checkoutStep < 5) {
|
||||||
|
dataLayer.push({
|
||||||
|
event: 'checkout_abandon',
|
||||||
|
last_step: checkoutStep,
|
||||||
|
step_name: ['cart', 'checkout', 'shipping', 'payment', 'complete'][checkoutStep - 1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Value Consistency Check
|
||||||
|
|
||||||
|
Ensure `value` matches across events:
|
||||||
|
|
||||||
|
```
|
||||||
|
view_cart.value = sum(items.price * items.quantity)
|
||||||
|
begin_checkout.value = view_cart.value
|
||||||
|
add_shipping_info.value = begin_checkout.value
|
||||||
|
add_payment_info.value = add_shipping_info.value
|
||||||
|
purchase.value = add_payment_info.value + shipping + tax - discount
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Duplicate Purchase Events
|
||||||
|
**Problem**: Same order tracked multiple times
|
||||||
|
**Solution**:
|
||||||
|
```javascript
|
||||||
|
// Check if already tracked
|
||||||
|
const txId = "T_12345";
|
||||||
|
if (!sessionStorage.getItem('purchase_' + txId)) {
|
||||||
|
dataLayer.push({ event: 'purchase', ... });
|
||||||
|
sessionStorage.setItem('purchase_' + txId, 'true');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Missing Items in Later Steps
|
||||||
|
**Problem**: Items present in view_cart but missing in purchase
|
||||||
|
**Solution**: Store cart data in session and reuse
|
||||||
|
|
||||||
|
### Inconsistent Currency
|
||||||
|
**Problem**: Some events use USD, others KRW
|
||||||
|
**Solution**: Standardize currency across all events
|
||||||
|
|
||||||
|
### Wrong Value Calculation
|
||||||
|
**Problem**: purchase.value doesn't include tax/shipping
|
||||||
|
**Solution**:
|
||||||
|
```
|
||||||
|
purchase.value = subtotal + tax + shipping - discount
|
||||||
|
```
|
||||||
|
|
||||||
|
## Korean E-commerce Platforms
|
||||||
|
|
||||||
|
### Cafe24
|
||||||
|
Custom dataLayer variable names - check documentation
|
||||||
|
|
||||||
|
### Shopify Korea
|
||||||
|
Standard GA4 format with `Shopify.checkout` object
|
||||||
|
|
||||||
|
### WooCommerce
|
||||||
|
Use official GA4 plugin or custom implementation
|
||||||
|
|
||||||
|
### Naver SmartStore
|
||||||
|
Separate Naver Analytics implementation required
|
||||||
|
|
||||||
|
## Checkout Flow Checklist
|
||||||
|
|
||||||
|
- [ ] view_cart fires on cart page load
|
||||||
|
- [ ] begin_checkout fires on checkout button click
|
||||||
|
- [ ] add_shipping_info includes shipping_tier
|
||||||
|
- [ ] add_payment_info includes payment_type
|
||||||
|
- [ ] purchase has unique transaction_id
|
||||||
|
- [ ] All events have consistent items array
|
||||||
|
- [ ] Currency is consistent across all events
|
||||||
|
- [ ] Value calculations are accurate
|
||||||
|
- [ ] ecommerce object cleared before each push
|
||||||
|
- [ ] Purchase event fires only once per order
|
||||||
211
ourdigital-custom-skills/13-gtm-audit/docs/common_issues.md
Normal file
211
ourdigital-custom-skills/13-gtm-audit/docs/common_issues.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Common GTM Issues & Fixes
|
||||||
|
|
||||||
|
## Container Issues
|
||||||
|
|
||||||
|
### GTM Not Firing
|
||||||
|
**Symptoms**: No GTM requests in network tab
|
||||||
|
**Causes**:
|
||||||
|
1. Script blocked by ad blocker
|
||||||
|
2. Script placed after closing body tag
|
||||||
|
3. JavaScript error before GTM loads
|
||||||
|
4. Consent management blocking GTM
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
```html
|
||||||
|
<!-- Place immediately after opening <head> tag -->
|
||||||
|
<script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Containers Conflict
|
||||||
|
**Symptoms**: Duplicate events, inconsistent data
|
||||||
|
**Causes**:
|
||||||
|
1. Legacy container not removed
|
||||||
|
2. Different teams installed separate containers
|
||||||
|
3. Theme/plugin auto-installed GTM
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. Audit all containers in source
|
||||||
|
2. Consolidate to single container
|
||||||
|
3. Use GTM environments for staging/prod
|
||||||
|
|
||||||
|
### Container ID Mismatch
|
||||||
|
**Symptoms**: Tags not firing, wrong property receiving data
|
||||||
|
**Causes**:
|
||||||
|
1. Dev/staging container on production
|
||||||
|
2. Copy-paste error during installation
|
||||||
|
|
||||||
|
**Fix**: Verify container ID matches GTM account
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DataLayer Issues
|
||||||
|
|
||||||
|
### DataLayer Not Initialized
|
||||||
|
**Symptoms**: First push events lost
|
||||||
|
**Code Error**:
|
||||||
|
```javascript
|
||||||
|
// Wrong - GTM loads before dataLayer exists
|
||||||
|
<script>GTM snippet</script>
|
||||||
|
dataLayer.push({...});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
```javascript
|
||||||
|
// Correct - Initialize dataLayer first
|
||||||
|
<script>window.dataLayer = window.dataLayer || [];</script>
|
||||||
|
<script>GTM snippet</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Case Sensitivity Issues
|
||||||
|
**Symptoms**: Triggers not matching
|
||||||
|
**Example**:
|
||||||
|
```javascript
|
||||||
|
// DataLayer pushes "AddToCart"
|
||||||
|
dataLayer.push({ event: "AddToCart" });
|
||||||
|
|
||||||
|
// But GTM trigger looks for "addToCart" - won't match!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix**: Standardize event naming (recommend lowercase with underscores)
|
||||||
|
|
||||||
|
### Wrong Data Types
|
||||||
|
**Symptoms**: Calculations wrong in GA4, missing data
|
||||||
|
**Example**:
|
||||||
|
```javascript
|
||||||
|
// Wrong - price as string
|
||||||
|
dataLayer.push({ ecommerce: { value: "29.99" }});
|
||||||
|
|
||||||
|
// Correct - price as number
|
||||||
|
dataLayer.push({ ecommerce: { value: 29.99 }});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timing Issues
|
||||||
|
**Symptoms**: Events fire before data available
|
||||||
|
**Cause**: DataLayer push happens after tag fires
|
||||||
|
|
||||||
|
**Fix**: Use "Custom Event" trigger instead of "Page View"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tag Issues
|
||||||
|
|
||||||
|
### Tag Not Firing
|
||||||
|
|
||||||
|
**Checklist**:
|
||||||
|
1. ✓ Trigger conditions met?
|
||||||
|
2. ✓ Trigger enabled?
|
||||||
|
3. ✓ Tag not paused?
|
||||||
|
4. ✓ No blocking triggers active?
|
||||||
|
5. ✓ Consent mode not blocking?
|
||||||
|
|
||||||
|
**Debug Steps**:
|
||||||
|
1. GTM Preview > Check Tags Fired
|
||||||
|
2. Verify trigger shows green check
|
||||||
|
3. Check Variables tab for expected values
|
||||||
|
|
||||||
|
### Duplicate Tag Firing
|
||||||
|
**Symptoms**: Events counted 2x in GA4
|
||||||
|
**Causes**:
|
||||||
|
1. Multiple triggers on same action
|
||||||
|
2. Page re-renders triggering again
|
||||||
|
3. SPA virtual pageviews firing multiple times
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. Add "Once per event" tag firing option
|
||||||
|
2. Use trigger groups to control firing
|
||||||
|
3. Add conditions to prevent re-firing
|
||||||
|
|
||||||
|
### Wrong Parameters Sent
|
||||||
|
**Symptoms**: Data appears in wrong fields in GA4
|
||||||
|
**Debug**:
|
||||||
|
1. GTM Preview > Tags > Show fired tag
|
||||||
|
2. Check "Values" sent with tag
|
||||||
|
3. Compare with expected parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E-commerce Issues
|
||||||
|
|
||||||
|
### Missing Transaction ID
|
||||||
|
**Symptoms**: Duplicate purchases counted
|
||||||
|
**Fix**: Ensure unique `transaction_id` generated server-side
|
||||||
|
|
||||||
|
### Items Array Empty
|
||||||
|
**Symptoms**: Revenue tracked but no products
|
||||||
|
**Check**: `ecommerce.items` array populated
|
||||||
|
|
||||||
|
### Value Mismatch
|
||||||
|
**Symptoms**: Revenue doesn't match actual
|
||||||
|
**Causes**:
|
||||||
|
1. Tax/shipping included inconsistently
|
||||||
|
2. Currency conversion issues
|
||||||
|
3. Discount applied incorrectly
|
||||||
|
|
||||||
|
### Purchase Event Fires Multiple Times
|
||||||
|
**Symptoms**: Same order tracked 2-3x
|
||||||
|
**Causes**:
|
||||||
|
1. Page refresh on confirmation
|
||||||
|
2. Browser back button
|
||||||
|
3. Email link revisit
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
```javascript
|
||||||
|
// Check if already tracked
|
||||||
|
if (!sessionStorage.getItem('purchase_' + transaction_id)) {
|
||||||
|
dataLayer.push({ event: 'purchase', ... });
|
||||||
|
sessionStorage.setItem('purchase_' + transaction_id, 'true');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Consent Mode Issues
|
||||||
|
|
||||||
|
### Tags Blocked by Consent
|
||||||
|
**Symptoms**: Tags show "Blocked by consent" in Preview
|
||||||
|
**Fix**:
|
||||||
|
1. Verify consent mode implementation
|
||||||
|
2. Check default consent state
|
||||||
|
3. Test with consent granted
|
||||||
|
|
||||||
|
### Consent Not Updating
|
||||||
|
**Symptoms**: Tags stay blocked after user accepts
|
||||||
|
**Fix**: Verify `gtag('consent', 'update', {...})` fires on accept
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SPA (Single Page App) Issues
|
||||||
|
|
||||||
|
### Pageviews Not Tracking Navigation
|
||||||
|
**Symptoms**: Only initial pageview tracked
|
||||||
|
**Cause**: No page reload on route change
|
||||||
|
|
||||||
|
**Fix**: Implement History Change trigger or custom event:
|
||||||
|
```javascript
|
||||||
|
// On route change
|
||||||
|
dataLayer.push({
|
||||||
|
event: 'virtual_pageview',
|
||||||
|
page_path: newPath,
|
||||||
|
page_title: newTitle
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events Fire on Old Page Data
|
||||||
|
**Symptoms**: Wrong page_path in events
|
||||||
|
**Fix**: Update page variables before event push
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Issues
|
||||||
|
|
||||||
|
### Tags Slowing Page Load
|
||||||
|
**Symptoms**: High LCP, slow TTI
|
||||||
|
**Causes**:
|
||||||
|
1. Too many synchronous tags
|
||||||
|
2. Large third-party scripts
|
||||||
|
3. Tags in wrong firing sequence
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. Use tag sequencing
|
||||||
|
2. Load non-critical tags on Window Loaded
|
||||||
|
3. Defer marketing tags
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
# DataLayer Validation Reference
|
||||||
|
|
||||||
|
## DataLayer Structure Basics
|
||||||
|
|
||||||
|
### Proper Initialization
|
||||||
|
```javascript
|
||||||
|
// Must appear BEFORE GTM script
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
</script>
|
||||||
|
<!-- GTM script here -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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');
|
||||||
|
```
|
||||||
216
ourdigital-custom-skills/13-gtm-audit/docs/ecommerce_schema.md
Normal file
216
ourdigital-custom-skills/13-gtm-audit/docs/ecommerce_schema.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# E-commerce DataLayer Schema Reference
|
||||||
|
|
||||||
|
## GA4 E-commerce Structure
|
||||||
|
|
||||||
|
### Items Array Schema
|
||||||
|
Every e-commerce event requires an `items` array:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
items: [{
|
||||||
|
// Required
|
||||||
|
item_id: "SKU_12345",
|
||||||
|
item_name: "Blue T-Shirt",
|
||||||
|
|
||||||
|
// Recommended
|
||||||
|
affiliation: "Store Name",
|
||||||
|
coupon: "SUMMER_SALE",
|
||||||
|
discount: 5.00,
|
||||||
|
index: 0,
|
||||||
|
item_brand: "Brand Name",
|
||||||
|
item_category: "Apparel",
|
||||||
|
item_category2: "Men",
|
||||||
|
item_category3: "Shirts",
|
||||||
|
item_category4: "T-Shirts",
|
||||||
|
item_category5: "Short Sleeve",
|
||||||
|
item_list_id: "related_products",
|
||||||
|
item_list_name: "Related Products",
|
||||||
|
item_variant: "Blue/Large",
|
||||||
|
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear Previous E-commerce Data
|
||||||
|
Always clear before new e-commerce event:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "view_item",
|
||||||
|
ecommerce: {
|
||||||
|
// new data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Purchase Flow
|
||||||
|
|
||||||
|
### 1. Product List View
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "view_item_list",
|
||||||
|
ecommerce: {
|
||||||
|
item_list_id: "category_results",
|
||||||
|
item_list_name: "Category Results",
|
||||||
|
items: [
|
||||||
|
{ item_id: "SKU_001", item_name: "Product 1", index: 0, price: 29.99 },
|
||||||
|
{ item_id: "SKU_002", item_name: "Product 2", index: 1, price: 39.99 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Product Click
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "select_item",
|
||||||
|
ecommerce: {
|
||||||
|
item_list_id: "category_results",
|
||||||
|
item_list_name: "Category Results",
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Product 1",
|
||||||
|
price: 29.99
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Product Detail View
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "view_item",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 29.99,
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Product 1",
|
||||||
|
item_brand: "Brand",
|
||||||
|
item_category: "Category",
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Add to Cart
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "add_to_cart",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 29.99,
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Product 1",
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. View Cart
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "view_cart",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 59.98,
|
||||||
|
items: [
|
||||||
|
{ item_id: "SKU_001", item_name: "Product 1", price: 29.99, quantity: 1 },
|
||||||
|
{ item_id: "SKU_002", item_name: "Product 2", price: 29.99, quantity: 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Begin Checkout
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "begin_checkout",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 59.98,
|
||||||
|
coupon: "DISCOUNT10",
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Add Shipping Info
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "add_shipping_info",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 59.98,
|
||||||
|
shipping_tier: "Standard",
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Add Payment Info
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "add_payment_info",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 59.98,
|
||||||
|
payment_type: "Credit Card",
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Purchase
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({ ecommerce: null });
|
||||||
|
dataLayer.push({
|
||||||
|
event: "purchase",
|
||||||
|
ecommerce: {
|
||||||
|
transaction_id: "T_12345",
|
||||||
|
value: 65.97,
|
||||||
|
tax: 4.99,
|
||||||
|
shipping: 5.99,
|
||||||
|
currency: "USD",
|
||||||
|
coupon: "DISCOUNT10",
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_001",
|
||||||
|
item_name: "Product 1",
|
||||||
|
affiliation: "Online Store",
|
||||||
|
coupon: "DISCOUNT10",
|
||||||
|
discount: 3.00,
|
||||||
|
item_brand: "Brand",
|
||||||
|
item_category: "Category",
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Korean E-commerce Considerations
|
||||||
|
|
||||||
|
### Currency
|
||||||
|
```javascript
|
||||||
|
currency: "KRW",
|
||||||
|
value: 35000 // No decimals for KRW
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Korean Platform Integrations
|
||||||
|
- Cafe24: Uses custom dataLayer structure
|
||||||
|
- Shopify Korea: Standard GA4 format
|
||||||
|
- Naver SmartStore: Custom pixel implementation
|
||||||
157
ourdigital-custom-skills/13-gtm-audit/docs/form_tracking.md
Normal file
157
ourdigital-custom-skills/13-gtm-audit/docs/form_tracking.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Form Tracking Reference
|
||||||
|
|
||||||
|
## GA4 Form Events
|
||||||
|
|
||||||
|
### form_start
|
||||||
|
Fires on first interaction with form field.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "form_start",
|
||||||
|
form_id: "contact-form",
|
||||||
|
form_name: "Contact Us",
|
||||||
|
form_destination: "/submit-contact"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### form_submit
|
||||||
|
Fires on successful form submission.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "form_submit",
|
||||||
|
form_id: "contact-form",
|
||||||
|
form_name: "Contact Us",
|
||||||
|
form_destination: "/submit-contact",
|
||||||
|
form_submit_text: "Send Message"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### generate_lead
|
||||||
|
Fires when form generates a qualified lead.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "generate_lead",
|
||||||
|
currency: "USD",
|
||||||
|
value: 100, // Estimated lead value
|
||||||
|
form_id: "quote-request"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Form Field Events (Custom)
|
||||||
|
|
||||||
|
### field_focus
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "field_focus",
|
||||||
|
form_id: "signup-form",
|
||||||
|
field_name: "email",
|
||||||
|
field_type: "email"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### field_complete
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "field_complete",
|
||||||
|
form_id: "signup-form",
|
||||||
|
field_name: "email",
|
||||||
|
field_type: "email",
|
||||||
|
is_valid: true
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### field_error
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "field_error",
|
||||||
|
form_id: "signup-form",
|
||||||
|
field_name: "email",
|
||||||
|
error_message: "Invalid email format"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Form Abandonment Tracking
|
||||||
|
|
||||||
|
### Detecting Abandonment
|
||||||
|
Track when user leaves form without submitting:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Track form start
|
||||||
|
let formStarted = false;
|
||||||
|
document.querySelectorAll('form input, form select, form textarea')
|
||||||
|
.forEach(field => {
|
||||||
|
field.addEventListener('focus', function() {
|
||||||
|
if (!formStarted) {
|
||||||
|
formStarted = true;
|
||||||
|
dataLayer.push({ event: 'form_start', form_id: this.form.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track abandonment on page leave
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (formStarted && !formSubmitted) {
|
||||||
|
dataLayer.push({
|
||||||
|
event: 'form_abandon',
|
||||||
|
form_id: 'contact-form',
|
||||||
|
last_field: lastFocusedField,
|
||||||
|
fields_completed: completedFieldCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## GTM Trigger Configuration
|
||||||
|
|
||||||
|
### Form Submission Trigger
|
||||||
|
| Setting | Value |
|
||||||
|
|---------|-------|
|
||||||
|
| Trigger Type | Form Submission |
|
||||||
|
| Wait for Tags | Check (if AJAX form) |
|
||||||
|
| Check Validation | Check |
|
||||||
|
| Form ID | equals `contact-form` |
|
||||||
|
|
||||||
|
### Form Start Trigger (Custom Event)
|
||||||
|
| Setting | Value |
|
||||||
|
|---------|-------|
|
||||||
|
| Trigger Type | Custom Event |
|
||||||
|
| Event Name | form_start |
|
||||||
|
| Fire On | All Custom Events |
|
||||||
|
|
||||||
|
## Common Form Types & Tracking
|
||||||
|
|
||||||
|
### Contact Forms
|
||||||
|
Events: `form_start`, `form_submit`, `generate_lead`
|
||||||
|
|
||||||
|
### Newsletter Signup
|
||||||
|
Events: `form_start`, `form_submit`, `sign_up`
|
||||||
|
|
||||||
|
### Login Forms
|
||||||
|
Events: `form_start`, `login`
|
||||||
|
|
||||||
|
### Search Forms
|
||||||
|
Events: `search` (with search_term parameter)
|
||||||
|
|
||||||
|
### Multi-Step Forms
|
||||||
|
Track each step:
|
||||||
|
```javascript
|
||||||
|
dataLayer.push({
|
||||||
|
event: "form_step",
|
||||||
|
form_id: "checkout-form",
|
||||||
|
step_number: 2,
|
||||||
|
step_name: "Shipping Address"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
|
||||||
|
- [ ] Form has id or name attribute
|
||||||
|
- [ ] All required fields have names
|
||||||
|
- [ ] Submit button identifiable
|
||||||
|
- [ ] form_start fires on first interaction
|
||||||
|
- [ ] form_submit fires only on success
|
||||||
|
- [ ] generate_lead has value parameter
|
||||||
|
- [ ] Error events track validation failures
|
||||||
|
- [ ] Abandonment tracking implemented (optional)
|
||||||
177
ourdigital-custom-skills/13-gtm-audit/docs/ga4_events.md
Normal file
177
ourdigital-custom-skills/13-gtm-audit/docs/ga4_events.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# GA4 Recommended Events Reference
|
||||||
|
|
||||||
|
## Automatically Collected Events
|
||||||
|
Events GA4 collects without configuration:
|
||||||
|
- `first_visit` - First time user visits
|
||||||
|
- `session_start` - Session begins
|
||||||
|
- `page_view` - Page loads (enhanced measurement)
|
||||||
|
- `scroll` - 90% scroll depth
|
||||||
|
- `click` - Outbound link clicks
|
||||||
|
- `file_download` - File download clicks
|
||||||
|
- `video_start`, `video_progress`, `video_complete` - YouTube embeds
|
||||||
|
|
||||||
|
## E-commerce Events (Required Parameters)
|
||||||
|
|
||||||
|
### view_item_list
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "view_item_list",
|
||||||
|
ecommerce: {
|
||||||
|
item_list_id: "related_products",
|
||||||
|
item_list_name: "Related Products",
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_12345", // required
|
||||||
|
item_name: "Product Name", // required
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### view_item
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "view_item",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 29.99,
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_12345", // required
|
||||||
|
item_name: "Product Name", // required
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### add_to_cart
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "add_to_cart",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 29.99,
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_12345", // required
|
||||||
|
item_name: "Product Name", // required
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### begin_checkout
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "begin_checkout",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 99.99,
|
||||||
|
coupon: "SUMMER_SALE",
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### add_payment_info
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "add_payment_info",
|
||||||
|
ecommerce: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 99.99,
|
||||||
|
payment_type: "credit_card",
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### purchase
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "purchase",
|
||||||
|
ecommerce: {
|
||||||
|
transaction_id: "T12345", // required, must be unique
|
||||||
|
value: 99.99, // required
|
||||||
|
currency: "USD", // required
|
||||||
|
tax: 4.99,
|
||||||
|
shipping: 5.99,
|
||||||
|
coupon: "SUMMER_SALE",
|
||||||
|
items: [{
|
||||||
|
item_id: "SKU_12345", // required
|
||||||
|
item_name: "Product Name",// required
|
||||||
|
price: 29.99,
|
||||||
|
quantity: 2
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lead Generation Events
|
||||||
|
|
||||||
|
### generate_lead
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "generate_lead",
|
||||||
|
currency: "USD",
|
||||||
|
value: 100 // estimated lead value
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### sign_up
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "sign_up",
|
||||||
|
method: "email" // or "google", "facebook", etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### login
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "login",
|
||||||
|
method: "email"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Engagement Events
|
||||||
|
|
||||||
|
### search
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "search",
|
||||||
|
search_term: "blue shoes"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### share
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
event: "share",
|
||||||
|
method: "twitter",
|
||||||
|
content_type: "article",
|
||||||
|
item_id: "article_123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameter Validation Rules
|
||||||
|
|
||||||
|
| Parameter | Type | Max Length | Notes |
|
||||||
|
|-----------|------|------------|-------|
|
||||||
|
| event name | string | 40 chars | No spaces, alphanumeric + underscore |
|
||||||
|
| item_id | string | 100 chars | Required for e-commerce |
|
||||||
|
| item_name | string | 100 chars | Required for e-commerce |
|
||||||
|
| currency | string | 3 chars | ISO 4217 format (USD, KRW, etc.) |
|
||||||
|
| transaction_id | string | 100 chars | Must be unique per transaction |
|
||||||
|
| value | number | - | Numeric, no currency symbols |
|
||||||
|
|
||||||
|
## Common Validation Errors
|
||||||
|
|
||||||
|
1. **Missing required params**: `item_id` or `item_name` not in items array
|
||||||
|
2. **Wrong data type**: `value` as string instead of number
|
||||||
|
3. **Duplicate transaction_id**: Same ID used for multiple purchases
|
||||||
|
4. **Empty items array**: E-commerce event with no items
|
||||||
|
5. **Invalid currency**: Currency code not in ISO 4217 format
|
||||||
115
ourdigital-custom-skills/13-gtm-audit/docs/report_template.md
Normal file
115
ourdigital-custom-skills/13-gtm-audit/docs/report_template.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# GTM Audit Report Template
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
| Metric | Status |
|
||||||
|
|--------|--------|
|
||||||
|
| Container Installed | ✅ / ❌ |
|
||||||
|
| Container Valid | ✅ / ❌ |
|
||||||
|
| DataLayer Active | ✅ / ❌ |
|
||||||
|
| Tags Firing | X of Y |
|
||||||
|
| Critical Issues | X |
|
||||||
|
| Warnings | X |
|
||||||
|
|
||||||
|
## Container Status
|
||||||
|
|
||||||
|
**Container ID**: GTM-XXXXXX
|
||||||
|
**Installation Position**: head / body
|
||||||
|
**Multiple Containers**: Yes / No
|
||||||
|
**Noscript Fallback**: Present / Missing
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
- [ ] Issue description
|
||||||
|
|
||||||
|
## DataLayer Analysis
|
||||||
|
|
||||||
|
### Events Captured
|
||||||
|
| Event Name | Count | Has Issues |
|
||||||
|
|------------|-------|------------|
|
||||||
|
| page_view | 1 | No |
|
||||||
|
| add_to_cart | 0 | - |
|
||||||
|
|
||||||
|
### DataLayer Quality
|
||||||
|
- [ ] Initialized before GTM
|
||||||
|
- [ ] Standard event naming
|
||||||
|
- [ ] Correct data types
|
||||||
|
- [ ] E-commerce structure valid
|
||||||
|
|
||||||
|
## Tag Firing Report
|
||||||
|
|
||||||
|
### Tags Fired ✅
|
||||||
|
| Destination | Events | Parameters |
|
||||||
|
|-------------|--------|------------|
|
||||||
|
| GA4 | page_view | page_location, page_title |
|
||||||
|
| Meta Pixel | PageView | - |
|
||||||
|
|
||||||
|
### Tags Not Detected ⚠️
|
||||||
|
| Expected Tag | Reason | Priority |
|
||||||
|
|--------------|--------|----------|
|
||||||
|
| GA4 purchase | Event not triggered | High |
|
||||||
|
|
||||||
|
## Network Request Analysis
|
||||||
|
|
||||||
|
Total requests captured: X
|
||||||
|
|
||||||
|
### By Destination
|
||||||
|
| Destination | Requests | Status |
|
||||||
|
|-------------|----------|--------|
|
||||||
|
| GA4 | X | ✅ |
|
||||||
|
| Meta | X | ✅ |
|
||||||
|
| Google Ads | 0 | ⚠️ |
|
||||||
|
|
||||||
|
## Issues & Recommendations
|
||||||
|
|
||||||
|
### Critical 🔴
|
||||||
|
1. **Issue Title**
|
||||||
|
- Description
|
||||||
|
- Impact
|
||||||
|
- Recommended Fix
|
||||||
|
|
||||||
|
### Warning 🟡
|
||||||
|
1. **Issue Title**
|
||||||
|
- Description
|
||||||
|
- Recommended Fix
|
||||||
|
|
||||||
|
### Info 🔵
|
||||||
|
1. **Issue Title**
|
||||||
|
- Description
|
||||||
|
|
||||||
|
## Action Items Checklist
|
||||||
|
|
||||||
|
### Immediate (Critical)
|
||||||
|
- [ ] Action item 1
|
||||||
|
- [ ] Action item 2
|
||||||
|
|
||||||
|
### Short-term (This Week)
|
||||||
|
- [ ] Action item 3
|
||||||
|
|
||||||
|
### Long-term (This Month)
|
||||||
|
- [ ] Action item 4
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
- URL Audited: https://example.com
|
||||||
|
- Audit Timestamp: YYYY-MM-DD HH:MM:SS
|
||||||
|
- Browser: Chromium (headless)
|
||||||
|
- Viewport: 1920x1080
|
||||||
|
|
||||||
|
### Raw Data
|
||||||
|
Full JSON report available at: `gtm_audit_report.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Tag Destination Reference
|
||||||
|
|
||||||
|
| Tag Type | Network Pattern |
|
||||||
|
|----------|-----------------|
|
||||||
|
| GA4 | google-analytics.com/g/collect |
|
||||||
|
| UA (Legacy) | google-analytics.com/collect |
|
||||||
|
| Google Ads | googleads.g.doubleclick.net |
|
||||||
|
| Meta Pixel | facebook.com/tr |
|
||||||
|
| LinkedIn | px.ads.linkedin.com |
|
||||||
|
| TikTok | analytics.tiktok.com |
|
||||||
|
| Kakao | pixel.kakao.com |
|
||||||
|
| Naver | wcs.naver.com |
|
||||||
1387
ourdigital-custom-skills/13-gtm-audit/gtm_audit.py
Normal file
1387
ourdigital-custom-skills/13-gtm-audit/gtm_audit.py
Normal file
File diff suppressed because it is too large
Load Diff
1
ourdigital-custom-skills/13-gtm-audit/requirements.txt
Normal file
1
ourdigital-custom-skills/13-gtm-audit/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
playwright>=1.40.0
|
||||||
30
ourdigital-custom-skills/13-gtm-audit/setup.sh
Normal file
30
ourdigital-custom-skills/13-gtm-audit/setup.sh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GTM Audit Tool Setup Script
|
||||||
|
|
||||||
|
echo "🔧 Setting up GTM Audit Tool..."
|
||||||
|
|
||||||
|
# Check Python
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "❌ Python 3 is required but not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Python 3 found"
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
echo "📦 Installing Python dependencies..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Install Playwright browsers
|
||||||
|
echo "🌐 Installing Playwright browsers..."
|
||||||
|
playwright install chromium
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Setup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Usage:"
|
||||||
|
echo " python gtm_audit.py --url 'https://example.com' --journey full"
|
||||||
|
echo ""
|
||||||
|
echo "For Claude Code:"
|
||||||
|
echo " claude"
|
||||||
|
echo " > Run a GTM audit on https://example.com"
|
||||||
Reference in New Issue
Block a user