Files
our-claude-skills/custom-skills/61-gtm-editor/SKILL.md
Andrew Yim 6ac547e78f
Some checks failed
Verify Skills / verify-skills (push) Has been cancelled
refactor(skills): clean skill names (strip NN- prefix from name:) — convention change
Adopt: directory keeps its NN- ordering prefix; skill `name:` is the clean form
without it (dir 16-seo-schema-validator → name: seo-schema-validator). Nicer to
invoke, matches the original desktop/SKILL.md names, still globally unique.

- 71 root SKILL.md: name: NN-foo → name: foo (flat skills + reference-curator suite).
  Plugins (mac-optimizer/multi-agent-guide/dintel-bootstrap) already clean; 95 already clean.
- scripts/migrate_skill_root.py: derive name = dirname minus NN- prefix (skill_name()).
- CLAUDE.md + SKILL-MIGRATION-GUIDE.md: document the dir-prefix / clean-name convention.

verify_skills.py: 0 name collisions across all renamed skills. (The ~/.claude/skills
symlinks were re-pointed to the clean names separately — filesystem only.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:11:01 +09:00

295 lines
11 KiB
Markdown

---
name: gtm-editor
description: >
GTM implementation toolkit. Creates, updates, and modifies GTM tags, triggers,
variables via API. Generates Custom HTML with ES5 compliance. Handles workspace
lifecycle, DOM analysis for trigger design, and dataLayer code generation.
Triggers on "create GTM tag", "generate dataLayer", "modify trigger", "update variable",
"inject tracking code", "write custom HTML", "manage GTM", "design tags",
"create conversion tag", "set up tracking".
NOT for auditing (use gtm-audit) or validation/QA (use gtm-validator).
---
# GTM Editor Skill
Create, modify, and deploy GTM configurations via API. Generates ES5-compliant Custom HTML tags.
## Available Tools
### GTM Container Management (DTM Agent MCP)
- `dtm_status` — Check auth and active account/container
- `dtm_set_account` / `dtm_set_container` — Switch context
- `dtm_list_tags` / `dtm_get_tag` / `dtm_create_tag` / `dtm_update_tag` / `dtm_delete_tag`
- `dtm_list_triggers` / `dtm_get_trigger` / `dtm_create_trigger` / `dtm_delete_trigger`
- `dtm_list_variables` / `dtm_get_variable` / `dtm_create_variable` / `dtm_delete_variable`
- `dtm_list_folders` / `dtm_list_workspaces` / `dtm_get_workspace_status`
- `dtm_list_versions` / `dtm_get_live_version` / `dtm_create_version`
### Browser Analysis (Chrome DevTools MCP)
- `navigate_page` — Load page for DOM analysis
- `evaluate_script` — Inspect forms, buttons, links, CSS selectors
- `take_snapshot` — Get page structure for trigger design
- `list_network_requests` — Verify tags fire after changes
### Reporting (Notion MCP)
- Write implementation plans and tag configurations to Notion
## Tagging Workflow: dataLayer First (MANDATORY)
**Always push complexity into the dataLayer, not GTM triggers.**
### Step 1: Design the dataLayer push FIRST
Before creating any GTM configuration, design the `dataLayer.push()` that the website should implement. Present this to the user as a code snippet matching their tech stack:
**For vanilla JS / static HTML (ES5):**
```javascript
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'generate_lead',
'lead_type': document.getElementById('requestType').value,
'form_id': 'contact-form'
});
```
**For React / Next.js / TypeScript:**
```typescript
declare global { interface Window { dataLayer: Record<string, any>[]; } }
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'generate_lead',
lead_type: selectedType,
form_id: 'contact-form',
});
```
**For Vue:**
```javascript
window.dataLayer?.push({
event: 'generate_lead',
lead_type: this.selectedType,
form_id: 'contact-form',
});
```
**For PHP / WordPress:**
```php
add_action('wp_footer', function() { ?>
<script>
document.getElementById('contact-form').addEventListener('submit', function() {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'generate_lead',
'lead_type': document.getElementById('requestType').value
});
});
</script>
<?php });
```
**ASK the user:** "Here's the dataLayer code for your developers. Can they add this to the page? What's your tech stack?"
### Step 2: Create simple GTM config
Once dataLayer push is agreed:
- **Trigger:** Custom Event `{{_event}}` equals `generate_lead` (simple, robust)
- **Variables:** DataLayer Variable for each parameter (e.g., `dlv - lead_type`)
- **Tag:** GA4 Event with `measurementIdOverride` + DLV parameters
### Step 3: cHTML fallback (LAST RESORT)
Only if user confirms they CANNOT modify the website code:
1. Create a Custom HTML tag that listens for DOM events
2. The cHTML pushes to dataLayer internally
3. A Custom Event trigger picks it up (same clean pattern)
**All cHTML must be ES5-compatible** (see ES5 section below).
### Decision Tree
```
Track user action?
├─ Can devs add dataLayer.push()? → YES → Simple CE trigger ✅
├─ Can't modify code, element has ID? → Use Click ID trigger ✅
├─ No ID? → Ask user to add one first
└─ Nothing possible? → cHTML + CE trigger (last resort)
```
---
## Core Capabilities
### 1. Tag Creation via GTM API
Create tags directly in GTM — no manual pasting. The API handles workspace lifecycle automatically (auto-creates new workspace if current one is published).
**GA4 Event Tags:**
```
GA4 event tags use type "gaawe" with:
- eventName: the GA4 event name (snake_case)
- measurementIdOverride: {{GA4 Measurement ID variable}} (NOT measurementId)
- eventParameters: list of name/value maps
```
**CRITICAL: measurementIdOverride, NOT measurementId**
GA4 event tags inherit from config — use `measurementIdOverride` to reference the measurement ID variable. The API rejects `measurementId`.
### 2. Trigger Design from DOM Analysis
Analyze page DOM to design precise triggers:
```javascript
// Extract forms
(function(){var forms=document.querySelectorAll('form');return JSON.stringify(Array.from(forms).map(function(f){return{id:f.id,action:f.action,fields:Array.from(f.querySelectorAll('input,textarea,select')).map(function(el){return{name:el.name,type:el.type,id:el.id}})}}));})()
// Extract CTAs and buttons
(function(){return JSON.stringify(Array.from(document.querySelectorAll('a[class*="btn"],button,[role="button"]')).map(function(el){return{text:(el.textContent||'').trim().substring(0,50),href:el.href||'',classes:el.className,id:el.id}}));})()
```
**Trigger types:**
| Type | Use For | Key Parameter |
|------|---------|---------------|
| `linkClick` | `<a>` elements | `autoEventFilter` with CSS selector |
| `click` | Any element | `filter` on Click ID/Classes |
| `formSubmission` | Form submit | `filter` on Form ID |
| `customEvent` | dataLayer events | `custom_event_filter` on `{{_event}}` |
| `scrollDepth` | Scroll tracking | `verticalThresholdsPercent` |
| `timer` | Time on page | `interval`, `limit` |
| `domReady` | DOM loaded | No filter needed |
### 3. Custom HTML Generation (ES5 MANDATORY)
GTM Custom HTML uses a JavaScript compiler that does NOT support ES2020+.
**FORBIDDEN:**
```javascript
// NO: optional chaining, arrow functions, const/let, template literals,
// destructuring, spread, async/await
```
**REQUIRED:**
```javascript
// YES: var, function(){}, string concatenation, explicit property access
(function() {
var form = document.getElementById('contact-form');
if (!form) return;
form.addEventListener('submit', function() {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'generate_lead',
'lead_type': form.querySelector('#requestType').value
});
});
})();
```
### 4. Workspace Lifecycle Management
GTM workspaces become **read-only after publishing**. The DTM Agent auto-handles this:
- If create/update fails with "Workspace is already submitted"
- Auto-creates a new workspace
- Retries the operation
- Caches the workspace ID for subsequent calls
**You don't need to manage workspaces manually.** Just call `dtm_create_tag` etc. and it works.
### 5. DataLayer Code Generation
Generate dataLayer push code for common tracking scenarios:
**E-commerce:**
```javascript
// purchase event
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null }); // Clear previous
window.dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': /* order ID */,
'currency': 'KRW',
'value': /* total */,
'items': [{ item_id: '', item_name: '', price: 0, quantity: 1 }]
}
});
```
**Forms/Leads:**
```javascript
window.dataLayer.push({
'event': 'generate_lead',
'lead_type': /* form type */,
'form_id': /* form element ID */
});
```
## Korean Market Patterns
| Context | Pattern |
|---------|---------|
| Currency | KRW (no decimals) |
| Payment buttons | 장바구니, 결제하기, 주문하기, 문의하기 |
| Payment methods | 카카오페이, 네이버페이, 토스, 신용카드 |
| Platforms | Kakao Pixel (`track.kakao.com`), Naver Analytics (`wcs.naver.net`) |
## Supported Tag Types
| GTM Type | Name | Platform |
|----------|------|----------|
| `googtag` | Google Tag | GA4 config |
| `gaawe` | GA4 Event | GA4 events |
| `gclidw` | Conversion Linker | Google Ads |
| `awct` | Ads Conversion | Google Ads |
| `html` | Custom HTML | Any (Meta, PostHog, Kakao, etc.) |
| `cvt_MQDKZ` | Clarity | Microsoft Clarity |
| `cvt_WF3R3` | Amplitude | Amplitude |
## FB Conversions API Integration
When creating tags that should trigger FB CAPI:
1. Check existing FBEventName mapping table (Regex Table variable)
2. If your custom event isn't mapped, add a new rule: `event_name → FB_Standard_Event`
3. Key mappings: `generate_lead → Lead`, `purchase → Purchase`, `page_view → PageView`
## Workflow
1. **Analyze**: Use Chrome DevTools to inspect the page (or receive from gtm-audit Mode D)
2. **Design**: Determine events, triggers, variables needed
3. **Create**: Use DTM Agent MCP tools to create in GTM
4. **Verify**: Hand off to gtm-validator for QA
5. **Document**: Write implementation details to Notion
## Trigger Design: IDs First (MANDATORY)
**Always prefer element `id` over CSS selectors.** IDs survive redesigns; CSS selectors break.
**Before creating ANY click/form trigger:**
1. Check if the target element has an `id` attribute via Chrome DevTools `evaluate_script`
2. **If it has an ID** → use `Click ID` or `Form ID` filter
3. **If it does NOT have an ID****ASK the user** before falling back to CSS:
> "This element doesn't have an ID. Can you add one to the website/app code?
> Suggested: `id="[section]-[element]-[action]"` e.g., `id="hero-btn-consult"`"
4. Only use CSS selectors when the user explicitly confirms they cannot modify the HTML
**Priority order:**
1. Element ID (`Click ID equals "hero-btn-consult"`)
2. Data attribute (`Click Element matches CSS [data-track="consult"]`)
3. Form ID (`Form ID equals "contact-form"`)
4. CSS selector (last resort — document which selectors are used)
**Suggested ID naming:** `[section]-[element]-[action]`
- Navigation: `nav-about`, `nav-services`, `nav-insights`
- CTAs: `hero-btn-consult`, `cta-btn-booking`
- Forms: `contact-form`, `newsletter-form`
- Footer: `footer-social-linkedin`, `footer-email`
## Rules
- Always use `dtm_status` first to verify auth and active container
- Always use `measurementIdOverride` for GA4 event tags (NOT `measurementId`)
- **Always prefer element IDs for triggers — ask user to add IDs before using CSS selectors**
- All Custom HTML must be ES5-compatible
- Use `--dry-run` conceptually — verify trigger selectors via Chrome DevTools before creating
- Rate limit: space API calls 1 second apart in batch operations
- Never delete tags without explicit user confirmation — move to Archive folder instead
- After creating tags, suggest user run gtm-validator to verify