From c02dafae1dff30bd325e4dec02efcd6c0a4111c5 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:32:45 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 18 + README.md | 3 + agents/.gitkeep | 0 agents/README.md | 30 + commands/.gitkeep | 0 commands/README.md | 31 + plugin.lock.json | 193 + skills/.gitkeep | 0 skills/README.md | 58 + skills/gtm-api/SKILL.md | 181 + skills/gtm-api/references/api-overview.md | 470 ++ skills/gtm-best-practices/SKILL.md | 150 + .../references/best-practices.md | 597 +++ skills/gtm-custom-templates/SKILL.md | 536 +++ .../assets/example_asset.txt | 24 + .../assets/tag-template-boilerplate.tpl | 168 + .../assets/variable-template-boilerplate.tpl | 133 + .../references/custom-templates-guide.md | 4051 +++++++++++++++++ .../references/sandboxed-javascript-api.md | 357 ++ .../references/template-examples.md | 149 + .../references/template-testing.md | 96 + skills/gtm-datalayer/SKILL.md | 474 ++ .../references/datalayer-best-practices.md | 572 +++ .../references/datalayer-fundamentals.md | 258 ++ .../references/ecommerce-datalayer.md | 542 +++ .../gtm-datalayer/references/spa-datalayer.md | 472 ++ skills/gtm-debugging/SKILL.md | 113 + .../references/debugging-testing.md | 642 +++ skills/gtm-general/SKILL.md | 334 ++ skills/gtm-general/references/gtm-overview.md | 59 + skills/gtm-setup/SKILL.md | 64 + skills/gtm-setup/references/gtm-overview.md | 59 + skills/gtm-tags/SKILL.md | 168 + .../google-rew-regular-expressions-syntax.txt | 462 ++ skills/gtm-tags/references/tags.md | 1039 +++++ skills/gtm-triggers/SKILL.md | 271 ++ .../google-rew-regular-expressions-syntax.txt | 462 ++ skills/gtm-triggers/references/triggers.md | 1552 +++++++ skills/gtm-variables/SKILL.md | 427 ++ .../google-rew-regular-expressions-syntax.txt | 462 ++ skills/gtm-variables/references/variables.md | 1606 +++++++ 41 files changed, 17283 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 agents/.gitkeep create mode 100644 agents/README.md create mode 100644 commands/.gitkeep create mode 100644 commands/README.md create mode 100644 plugin.lock.json create mode 100644 skills/.gitkeep create mode 100644 skills/README.md create mode 100644 skills/gtm-api/SKILL.md create mode 100644 skills/gtm-api/references/api-overview.md create mode 100644 skills/gtm-best-practices/SKILL.md create mode 100644 skills/gtm-best-practices/references/best-practices.md create mode 100644 skills/gtm-custom-templates/SKILL.md create mode 100644 skills/gtm-custom-templates/assets/example_asset.txt create mode 100644 skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl create mode 100644 skills/gtm-custom-templates/assets/variable-template-boilerplate.tpl create mode 100644 skills/gtm-custom-templates/references/custom-templates-guide.md create mode 100644 skills/gtm-custom-templates/references/sandboxed-javascript-api.md create mode 100644 skills/gtm-custom-templates/references/template-examples.md create mode 100644 skills/gtm-custom-templates/references/template-testing.md create mode 100644 skills/gtm-datalayer/SKILL.md create mode 100644 skills/gtm-datalayer/references/datalayer-best-practices.md create mode 100644 skills/gtm-datalayer/references/datalayer-fundamentals.md create mode 100644 skills/gtm-datalayer/references/ecommerce-datalayer.md create mode 100644 skills/gtm-datalayer/references/spa-datalayer.md create mode 100644 skills/gtm-debugging/SKILL.md create mode 100644 skills/gtm-debugging/references/debugging-testing.md create mode 100644 skills/gtm-general/SKILL.md create mode 100644 skills/gtm-general/references/gtm-overview.md create mode 100644 skills/gtm-setup/SKILL.md create mode 100644 skills/gtm-setup/references/gtm-overview.md create mode 100644 skills/gtm-tags/SKILL.md create mode 100644 skills/gtm-tags/references/google-rew-regular-expressions-syntax.txt create mode 100644 skills/gtm-tags/references/tags.md create mode 100644 skills/gtm-triggers/SKILL.md create mode 100644 skills/gtm-triggers/references/google-rew-regular-expressions-syntax.txt create mode 100644 skills/gtm-triggers/references/triggers.md create mode 100644 skills/gtm-variables/SKILL.md create mode 100644 skills/gtm-variables/references/google-rew-regular-expressions-syntax.txt create mode 100644 skills/gtm-variables/references/variables.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..c54205b --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "gtm-suite", + "description": "Complete Google Tag Manager suite with 10 specialized skills covering setup, configuration, implementation, debugging, and best practices. Includes GTM fundamentals, tags, triggers, variables, data layer architecture, debugging tools, custom templates, and API automation for comprehensive GTM mastery.", + "version": "1.0.0", + "author": { + "name": "Henrik Soederlund", + "email": "whom-wealthy.2z@icloud.com" + }, + "skills": [ + "./skills" + ], + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cc0c4e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# gtm-suite + +Complete Google Tag Manager suite with 10 specialized skills covering setup, configuration, implementation, debugging, and best practices. Includes GTM fundamentals, tags, triggers, variables, data layer architecture, debugging tools, custom templates, and API automation for comprehensive GTM mastery. diff --git a/agents/.gitkeep b/agents/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/agents/README.md b/agents/README.md new file mode 100644 index 0000000..1baed2e --- /dev/null +++ b/agents/README.md @@ -0,0 +1,30 @@ +# GTM Suite - Agents + +Add your GTM specialist agents here. + +## Suggested Agents + +- **gtm-specialist.md** - Expert in GTM architecture and best practices +- **gtm-debugger.md** - Troubleshooting and debugging specialist +- **gtm-implementation.md** - Implementation and migration expert + +## Agent Format + +```markdown +--- +description: Brief description of agent expertise +capabilities: ["capability1", "capability2", "capability3"] +--- + +# Agent Name + +Detailed description of the agent's role, expertise, and when Claude should invoke it. + +## Capabilities +- Specific task the agent excels at +- Another specialized capability +- When to use this agent + +## Examples +Provide examples of when this agent should be used. +``` diff --git a/commands/.gitkeep b/commands/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/commands/README.md b/commands/README.md new file mode 100644 index 0000000..31559d8 --- /dev/null +++ b/commands/README.md @@ -0,0 +1,31 @@ +# GTM Suite - Commands + +Add your GTM slash commands here. + +## Suggested Commands + +- **gtm-init.md** - Initialize GTM container setup +- **gtm-audit.md** - Audit current GTM configuration +- **gtm-migrate.md** - Migrate tags from old tracking system +- **gtm-test.md** - Test GTM configuration +- **gtm-export.md** - Export container configuration + +## Command Format + +```markdown +--- +description: Brief description of what this command does +--- + +# Command Name + +Detailed instructions for Claude on how to execute this command. + +## Steps +1. First step +2. Second step +3. Third step + +## Examples +Show examples of how users might invoke this command. +``` diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..2e7a6b6 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,193 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:henkisdabro/wookstar-claude-code-plugins:gtm-suite", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "da44553307143a94965b9ff8c493bdcdbbd86ddf", + "treeHash": "6b17ce84f9b83786d9f76618e2375605ba9f1ac8bdd0b2bd60a352a698f79e0c", + "generatedAt": "2025-11-28T10:17:24.959339Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "gtm-suite", + "description": "Complete Google Tag Manager suite with 10 specialized skills covering setup, configuration, implementation, debugging, and best practices. Includes GTM fundamentals, tags, triggers, variables, data layer architecture, debugging tools, custom templates, and API automation for comprehensive GTM mastery.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "05b90575127a947555159fbe66423f3920f203e3d4a4cc0d5aabdbba81617f7f" + }, + { + "path": "agents/.gitkeep", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "agents/README.md", + "sha256": "9733da8835bad1e33531fefd8e55aeacfc533ec93aae7bd9313b1ec8b6536386" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "7e33916b5dda95e40c2c3283979cd664ee9014618da0e9d8ed3de6de46095f0a" + }, + { + "path": "commands/.gitkeep", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "commands/README.md", + "sha256": "6ba0461debaedd35bf375a103a8c1076ac5ac4c94b3c5cd845ccd22a63368764" + }, + { + "path": "skills/.gitkeep", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "skills/README.md", + "sha256": "4c6bc67f212a3b59184f503ee4ea99024513f9ba6d99652536c9239930f4f654" + }, + { + "path": "skills/gtm-triggers/SKILL.md", + "sha256": "455afa01c1cc04d20babea78eca17d0530bf517c2d92495cf363d1de42d0cb9f" + }, + { + "path": "skills/gtm-triggers/references/triggers.md", + "sha256": "98973704c3fce1601fe152e823755c2476cb4ae6c0100bd343d8deb8a10dd1fd" + }, + { + "path": "skills/gtm-triggers/references/google-rew-regular-expressions-syntax.txt", + "sha256": "f6254043af086d3f6ae96e9ac84b846d12b1c980097f544e1accaa65b4041c68" + }, + { + "path": "skills/gtm-datalayer/SKILL.md", + "sha256": "7e51ad5889b6a5451490d21f159a28d516347560d5cba89f2a2ab695d9eec4e2" + }, + { + "path": "skills/gtm-datalayer/references/spa-datalayer.md", + "sha256": "9bf14b231e558f6093c0c2618bf5bf9e886bd168db72c026e049120b2e18530b" + }, + { + "path": "skills/gtm-datalayer/references/ecommerce-datalayer.md", + "sha256": "65425186c0b48529037f29cc52dd9f0fde295a86c596886e458c200f09eb1ea5" + }, + { + "path": "skills/gtm-datalayer/references/datalayer-fundamentals.md", + "sha256": "0a62a676734e2fa6d2139eb44749d5cb6b8b738a7e1b100701f3c739631a2ff8" + }, + { + "path": "skills/gtm-datalayer/references/datalayer-best-practices.md", + "sha256": "484a673f8f8bbc62aacf7fd85be3cfce1d5c54e801aa6d96072fcd732e11ea0b" + }, + { + "path": "skills/gtm-setup/SKILL.md", + "sha256": "8197e641a5d4b72156cf218e7e6511107466cd30ccf3ca5f25e24ec73d3f5f53" + }, + { + "path": "skills/gtm-setup/references/gtm-overview.md", + "sha256": "b5e984a4c3e6ba96aba169363cf1f67958bf3b4e5f8527e729c759b8fc052e31" + }, + { + "path": "skills/gtm-best-practices/SKILL.md", + "sha256": "fe43bb7c8f567e7d1b8276b69167cbf77c1865bf1ea208a3b8929fc008a9cb85" + }, + { + "path": "skills/gtm-best-practices/references/best-practices.md", + "sha256": "836ace6d2cdfe24fa6ed6f2580ccf964bb0811082b3e076e35bf38dbfe4a504f" + }, + { + "path": "skills/gtm-api/SKILL.md", + "sha256": "d46ad26b2fb39df1f280b37574f792f314259bced2b3a955092c190dbad6bd04" + }, + { + "path": "skills/gtm-api/references/api-overview.md", + "sha256": "a3caba3c3aca8035be028e3beb0a6ff2d878ed2483d087cdb0f354d93c65a1bb" + }, + { + "path": "skills/gtm-variables/SKILL.md", + "sha256": "223c1ef8214b9ab96e96b431348f19d411e84733bd7b51cae1f7570309fd0091" + }, + { + "path": "skills/gtm-variables/references/google-rew-regular-expressions-syntax.txt", + "sha256": "f6254043af086d3f6ae96e9ac84b846d12b1c980097f544e1accaa65b4041c68" + }, + { + "path": "skills/gtm-variables/references/variables.md", + "sha256": "398b3f6d8796f7ec31afb1a7f9ed3b05c415d3e6aed72dcc064593aaebd1e92b" + }, + { + "path": "skills/gtm-debugging/SKILL.md", + "sha256": "4ba23e7f4b740a0cc6f3593b283b4cc50255c9bcedaf79e80311fafcdaf81033" + }, + { + "path": "skills/gtm-debugging/references/debugging-testing.md", + "sha256": "74113f6259ee62d70b9c14bf9eed4672d6dc098e2161da8d253db976cddbb72f" + }, + { + "path": "skills/gtm-general/SKILL.md", + "sha256": "4cfd9af0fa219ce3e53b57be6b6ec470212f6e50d737dab3a8f8bd26299f6461" + }, + { + "path": "skills/gtm-general/references/gtm-overview.md", + "sha256": "b5e984a4c3e6ba96aba169363cf1f67958bf3b4e5f8527e729c759b8fc052e31" + }, + { + "path": "skills/gtm-tags/SKILL.md", + "sha256": "d2da7dc1e33700ad41aa8a2dd4e417137a0f030c01d851bc3c3564a68975a4ca" + }, + { + "path": "skills/gtm-tags/references/tags.md", + "sha256": "6068013a81247eee4ea1c0040f8f1e64325ec92051cc7150f4b6a7a8ab592cb8" + }, + { + "path": "skills/gtm-tags/references/google-rew-regular-expressions-syntax.txt", + "sha256": "f6254043af086d3f6ae96e9ac84b846d12b1c980097f544e1accaa65b4041c68" + }, + { + "path": "skills/gtm-custom-templates/SKILL.md", + "sha256": "797ffef532514527b0a639efbf6c53676d0c77e354339b3577fdcb1ac2fe964d" + }, + { + "path": "skills/gtm-custom-templates/references/custom-templates-guide.md", + "sha256": "588efee6568223b9b645336562bc60001551c64b7a8a4b89863737c1315df5db" + }, + { + "path": "skills/gtm-custom-templates/references/template-examples.md", + "sha256": "d10324376af6dcbe9967363d6982517e85d3f0268d3e1d48040daa92499b8944" + }, + { + "path": "skills/gtm-custom-templates/references/sandboxed-javascript-api.md", + "sha256": "be8a79ec1583be5d90ffddea31c925b3beac62392dccf132f0286af541c2ad6e" + }, + { + "path": "skills/gtm-custom-templates/references/template-testing.md", + "sha256": "d1a4c9177f4f883b41c3b6d6f1ca43ef3fb67d02f1ecd7c3bbd9ce146efed57c" + }, + { + "path": "skills/gtm-custom-templates/assets/variable-template-boilerplate.tpl", + "sha256": "936771fafbbc62425fdc8b1f85a62d0dfd3ae7caca9c6d865d3097537a8fea3f" + }, + { + "path": "skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl", + "sha256": "dc01abd58949f75e7d7ea21c81729bac5ce1b3d76356c676f0f3ceb5c6c927af" + }, + { + "path": "skills/gtm-custom-templates/assets/example_asset.txt", + "sha256": "3410b1e7d80c9431ac98834d98da8214e0a658fef14d32b86a75ddf2bbe6e2d4" + } + ], + "dirSha256": "6b17ce84f9b83786d9f76618e2375605ba9f1ac8bdd0b2bd60a352a698f79e0c" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/.gitkeep b/skills/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..0d0971d --- /dev/null +++ b/skills/README.md @@ -0,0 +1,58 @@ +# GTM Suite - Skills + +Add your GTM skills here. Each skill should be in its own directory with a SKILL.md file. + +## Suggested Skills + +``` +skills/ +├── event-tracking/ +│ ├── SKILL.md +│ ├── scripts/ +│ └── references/ +├── container-management/ +│ ├── SKILL.md +│ └── references/ +├── variable-configuration/ +│ ├── SKILL.md +│ └── references/ +├── trigger-setup/ +│ ├── SKILL.md +│ └── references/ +├── tag-templates/ +│ ├── SKILL.md +│ ├── assets/ +│ └── references/ +└── debugging-tools/ + ├── SKILL.md + ├── scripts/ + └── references/ +``` + +## Skill Format + +Each skill directory should contain: +- **SKILL.md** (required) - Main skill file with frontmatter +- **scripts/** (optional) - Executable scripts +- **references/** (optional) - Documentation to load as needed +- **assets/** (optional) - Templates and files for output + +### SKILL.md Structure + +```markdown +--- +name: skill-name +description: When this skill should be used +--- + +# Skill Name + +## Overview +What this skill does + +## Workflow +How to use this skill + +## Resources +Reference the bundled resources (scripts, references, assets) +``` diff --git a/skills/gtm-api/SKILL.md b/skills/gtm-api/SKILL.md new file mode 100644 index 0000000..94c36e1 --- /dev/null +++ b/skills/gtm-api/SKILL.md @@ -0,0 +1,181 @@ +--- +name: gtm-api +description: Expert guidance for Google Tag Manager REST API v2 including authentication, programmatic container management, automation scripts, bulk operations, and GTM configuration backup/export. Use when automating GTM operations, managing multiple containers, building GTM tools, syncing configurations, exporting GTM data programmatically, working with Python or Node.js GTM automation, or using service account JSON credentials. Requires google-api-python-client and google-auth packages for Python implementations. +--- + +# GTM API Automation + +## Overview + +This skill provides comprehensive expertise for the Google Tag Manager REST API v2, enabling programmatic management of GTM containers, tags, triggers, variables, and configurations. + +## When to Use This Skill + +Invoke this skill when: +- Automating GTM container management +- Building tools for GTM configuration backup/restore +- Syncing tags across multiple containers +- Bulk creating or updating tags, triggers, variables +- Exporting GTM configuration to JSON +- Migrating containers between accounts +- Programmatically publishing GTM versions +- Building custom GTM management dashboards +- Implementing GTM configuration as code +- Integrating GTM with CI/CD pipelines + +## GTM API Basics + +### Authentication + +Use OAuth 2.0 with service accounts (recommended for automation): + +```python +from google.oauth2 import service_account +from googleapiclient.discovery import build + +SCOPES = ['https://www.googleapis.com/auth/tagmanager.edit.containers'] +SERVICE_ACCOUNT_FILE = 'service-account-key.json' + +credentials = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_FILE, scopes=SCOPES) + +service = build('tagmanager', 'v2', credentials=credentials) +``` + +### API Resource Hierarchy + +``` +Account → Container → Workspace → Tags/Triggers/Variables +``` + +## Common Operations + +**List Containers:** +```python +containers = service.accounts().containers().list( + parent='accounts/123456' +).execute() +``` + +**Create Tag:** +```python +tag_body = { + 'name': 'GA4 Event', + 'type': 'gaawe', + 'parameter': [ + {'key': 'eventName', 'value': 'click', 'type': 'template'} + ], + 'firingTriggerId': ['trigger_id'] +} + +service.accounts().containers().workspaces().tags().create( + parent=workspace_path, body=tag_body +).execute() +``` + +**Publish Version:** +```python +version = service.accounts().containers().workspaces().create_version( + path=workspace_path, + body={'name': 'API Version', 'description': 'Auto-deployed'} +).execute() + +service.accounts().containers().versions().publish( + path=version['containerVersion']['path'] +).execute() +``` + +## Automation Patterns + +### Backup Container + +```python +def backup_container(workspace_path, output_file): + tags = service.accounts().containers().workspaces().tags().list( + parent=workspace_path + ).execute() + + triggers = service.accounts().containers().workspaces().triggers().list( + parent=workspace_path + ).execute() + + variables = service.accounts().containers().workspaces().variables().list( + parent=workspace_path + ).execute() + + with open(output_file, 'w') as f: + json.dump({ + 'tags': tags.get('tag', []), + 'triggers': triggers.get('trigger', []), + 'variables': variables.get('variable', []) + }, f, indent=2) +``` + +### Bulk Operations + +```python +def disable_all_tags(workspace_path): + tags = service.accounts().containers().workspaces().tags().list( + parent=workspace_path + ).execute() + + for tag in tags.get('tag', []): + tag['paused'] = True + service.accounts().containers().workspaces().tags().update( + path=tag['path'], body=tag + ).execute() +``` + +## Error Handling + +```python +from googleapiclient.errors import HttpError + +try: + tag = service.accounts().containers().workspaces().tags().create( + parent=workspace_path, body=tag_body + ).execute() +except HttpError as error: + if error.resp.status == 404: + print("Resource not found") + elif error.resp.status == 403: + print("Permission denied") +``` + +## Best Practices + +- Use service accounts for automation +- Implement rate limiting +- Always backup before modifications +- Test in workspaces before publishing +- Log all operations +- Handle errors gracefully +- Version control your configs + +## References + +- **references/api-overview.md** - Complete API reference + +## Integration with Other Skills + +- **gtm-general** - Understanding GTM concepts and architecture +- **gtm-tags** - Understanding tag structure for API operations +- **gtm-triggers** - Understanding trigger structure for API operations +- **gtm-variables** - Understanding variable structure for API operations +- **gtm-datalayer** - Creating data layer variables via API +- **gtm-custom-templates** - Managing templates programmatically +- **gtm-setup** - Container setup concepts for API automation + +## Quick Reference + +**Install:** `pip install google-api-python-client google-auth-oauthlib` + +**List:** `.list(parent=parent_path)` + +**Get:** `.get(path=resource_path)` + +**Create:** `.create(parent=parent_path, body=resource_body)` + +**Update:** `.update(path=resource_path, body=resource_body)` + +**Publish:** `.create_version()` then `.publish()` diff --git a/skills/gtm-api/references/api-overview.md b/skills/gtm-api/references/api-overview.md new file mode 100644 index 0000000..60f1ec6 --- /dev/null +++ b/skills/gtm-api/references/api-overview.md @@ -0,0 +1,470 @@ +# GTM API Overview + +**Sources**: +- https://developers.google.com/tag-platform/tag-manager/api/v2 +- https://developers.google.com/tag-platform/tag-manager/api/v2/devguide + +**Last Updated**: 2025-01-09 + +## Overview + +The Google Tag Manager API provides programmatic access to Google Tag Manager configuration data for authorized users. With this API, you can automate common tag management tasks, including creating containers, managing tags, triggers, variables, and publishing container versions. + +## Why Use the GTM API + +**Benefits:** +- **Automation**: Automate repetitive tasks like creating similar tags across containers +- **Scalability**: Manage multiple containers programmatically +- **Integration**: Integrate GTM with your existing workflows and tools +- **Version Control**: Implement custom version control and deployment workflows +- **Bulk Operations**: Create, update, or delete resources in bulk + +**Common Use Cases:** +- Deploying standard tag configurations across multiple containers +- Implementing custom approval workflows +- Syncing GTM configurations with external systems +- Creating automated testing for tag configurations +- Building custom GTM management interfaces + +## API Capabilities + +The GTM API allows you to manage: + +- **Accounts**: Access and list GTM accounts +- **Containers**: Create, read, update containers +- **Workspaces**: Manage draft configurations +- **Tags**: Create and configure tags programmatically +- **Triggers**: Set up firing conditions +- **Variables**: Define data sources +- **Folders**: Organize container elements +- **Built-in Variables**: Enable/disable built-in variables +- **Versions**: Create, publish, and manage versions +- **Environments**: Configure staging environments +- **User Permissions**: Manage account and container access + +## Authentication + +The GTM API uses **OAuth 2.0** for authentication and authorization. + +### OAuth 2.0 Scopes + +Choose the appropriate scope based on your needs: + +``` +https://www.googleapis.com/auth/tagmanager.readonly +Read-only access to all GTM data + +https://www.googleapis.com/auth/tagmanager.edit.containers +Edit access to container configurations + +https://www.googleapis.com/auth/tagmanager.delete.containers +Permission to delete containers + +https://www.googleapis.com/auth/tagmanager.edit.containerversions +Create and modify container versions + +https://www.googleapis.com/auth/tagmanager.publish +Publish container versions + +https://www.googleapis.com/auth/tagmanager.manage.users +Manage user permissions + +https://www.googleapis.com/auth/tagmanager.manage.accounts +Full account access +``` + +### Setup Steps + +1. **Create a Project** in Google Cloud Console +2. **Enable the GTM API** for your project +3. **Create OAuth 2.0 Credentials**: + - For web apps: OAuth 2.0 Client ID + - For server apps: Service Account +4. **Download credentials** JSON file +5. **Implement OAuth flow** in your application + +### Python Example + +```python +from google.oauth2.credentials import Credentials +from googleapiclient.discovery import build + +# Define scopes +scopes = ['https://www.googleapis.com/auth/tagmanager.edit.containers'] + +# Create service +service = build('tagmanager', 'v2', credentials=credentials) +``` + +### JavaScript Example + +```javascript +const CLIENT_ID = 'YOUR_CLIENT_ID'; +const SCOPES = [ + 'https://www.googleapis.com/auth/tagmanager.edit.containers' +]; + +gapi.client.load('tagmanager', 'v2', () => { + // API loaded +}); +``` + +## API Resources and Hierarchy + +GTM API follows a hierarchical structure: + +``` +Account (accounts/123456) + └─ Container (accounts/123456/containers/7890) + └─ Workspace (accounts/123456/containers/7890/workspaces/1) + ├─ Tags + ├─ Triggers + ├─ Variables + └─ Folders + └─ Versions + └─ Environments +``` + +## Common Operations + +### List Containers + +```python +# Python +account_path = 'accounts/123456' +containers = service.accounts().containers().list( + parent=account_path +).execute() + +for container in containers.get('container', []): + print(f"Container: {container['name']} ({container['containerId']})") +``` + +```javascript +// JavaScript +const request = gapi.client.tagmanager.accounts.containers.list({ + parent: 'accounts/123456' +}); + +request.execute((response) => { + response.container.forEach((container) => { + console.log(`Container: ${container.name} (${container.containerId})`); + }); +}); +``` + +### Create a Workspace + +```python +# Python +container_path = 'accounts/123456/containers/7890' +workspace = service.accounts().containers().workspaces().create( + parent=container_path, + body={'name': 'API Workspace', 'description': 'Created via API'} +).execute() +``` + +### Create a Tag + +```python +# Python +workspace_path = 'accounts/123456/containers/7890/workspaces/1' + +ga4_tag = { + 'name': 'GA4 Config Tag', + 'type': 'gaawe', # GA4 Configuration tag type + 'parameter': [ + {'key': 'measurementId', 'type': 'template', 'value': 'G-XXXXXXXXXX'} + ] +} + +tag = service.accounts().containers().workspaces().tags().create( + parent=workspace_path, + body=ga4_tag +).execute() +``` + +### Create a Trigger + +```python +# Python +pageview_trigger = { + 'name': 'All Pages', + 'type': 'PAGEVIEW' +} + +trigger = service.accounts().containers().workspaces().triggers().create( + parent=workspace_path, + body=pageview_trigger +).execute() +``` + +### Link Tag to Trigger + +```python +# Python +tag_path = 'accounts/123456/containers/7890/workspaces/1/tags/5' + +# Get existing tag +tag = service.accounts().containers().workspaces().tags().get( + path=tag_path +).execute() + +# Add trigger ID +tag['firingTriggerId'] = [trigger['triggerId']] + +# Update tag +updated_tag = service.accounts().containers().workspaces().tags().update( + path=tag_path, + body=tag +).execute() +``` + +### Create and Publish Version + +```python +# Python +workspace_path = 'accounts/123456/containers/7890/workspaces/1' + +# Create version +version = service.accounts().containers().workspaces().create_version( + path=workspace_path, + body={'name': 'Version 1', 'notes': 'Initial setup'} +).execute() + +# Publish version +published = service.accounts().containers().versions().publish( + path=version['containerVersion']['path'] +).execute() +``` + +## API Patterns + +### Pagination + +Some list operations return paginated results: + +```python +# Python +def list_all_tags(service, workspace_path): + tags = [] + page_token = None + + while True: + response = service.accounts().containers().workspaces().tags().list( + parent=workspace_path, + pageToken=page_token + ).execute() + + tags.extend(response.get('tag', [])) + page_token = response.get('nextPageToken') + + if not page_token: + break + + return tags +``` + +### Error Handling + +```python +# Python +from googleapiclient.errors import HttpError + +try: + tag = service.accounts().containers().workspaces().tags().create( + parent=workspace_path, + body=tag_data + ).execute() +except HttpError as error: + if error.resp.status == 409: + print("Tag already exists") + elif error.resp.status == 403: + print("Permission denied") + else: + print(f"An error occurred: {error}") +``` + +### Rate Limiting + +The API has the following quotas (default): +- **Queries per day**: 1,000 (can be increased) +- **Queries per 100 seconds**: 100 + +Implement retry logic with exponential backoff: + +```python +# Python +import time +from googleapiclient.errors import HttpError + +def api_call_with_retry(func, max_retries=5): + for attempt in range(max_retries): + try: + return func() + except HttpError as error: + if error.resp.status == 429: # Rate limit exceeded + wait_time = 2 ** attempt # Exponential backoff + time.sleep(wait_time) + else: + raise + + raise Exception("Max retries exceeded") +``` + +## Resource Paths + +All API operations use resource paths: + +``` +Account: accounts/{account_id} +Container: accounts/{account_id}/containers/{container_id} +Workspace: accounts/{account_id}/containers/{container_id}/workspaces/{workspace_id} +Tag: accounts/{account_id}/containers/{container_id}/workspaces/{workspace_id}/tags/{tag_id} +``` + +Use paths from API responses rather than constructing them manually when possible. + +## Best Practices + +### 1. Use Workspaces + +Always work within a workspace: +- Create a new workspace for API changes +- Make changes in the workspace +- Create and publish a version when ready +- Don't modify the default workspace + +### 2. Test Before Production + +```python +# Create test workspace +test_workspace = service.accounts().containers().workspaces().create( + parent=container_path, + body={'name': 'API Test', 'description': 'Testing API changes'} +).execute() + +# Make changes +# Test thoroughly +# Create version if satisfied +# Delete workspace if not needed +``` + +### 3. Version Everything + +Always create versions with meaningful notes: + +```python +version = service.accounts().containers().workspaces().create_version( + path=workspace_path, + body={ + 'name': 'Release 2.3.0', + 'notes': 'Added GA4 ecommerce tracking\n- Purchase event\n- Product views\n- Add to cart' + } +).execute() +``` + +### 4. Handle Permissions + +Check user permissions before operations: + +```python +try: + containers = service.accounts().containers().list( + parent='accounts/123456' + ).execute() +except HttpError as error: + if error.resp.status == 403: + print("User lacks permission to access this account") +``` + +### 5. Use Batch Requests + +For multiple operations, use batch requests: + +```python +from googleapiclient.http import BatchHttpRequest + +def callback(request_id, response, exception): + if exception: + print(f"Error: {exception}") + else: + print(f"Created: {response['name']}") + +batch = service.new_batch_http_request(callback=callback) + +for tag_data in tags_to_create: + batch.add(service.accounts().containers().workspaces().tags().create( + parent=workspace_path, + body=tag_data + )) + +batch.execute() +``` + +## Example: Complete Workflow + +```python +# 1. List accounts +accounts = service.accounts().list().execute() +account_path = accounts['account'][0]['path'] + +# 2. Find container +containers = service.accounts().containers().list(parent=account_path).execute() +container = next(c for c in containers['container'] if c['name'] == 'My Site') +container_path = container['path'] + +# 3. Create workspace +workspace = service.accounts().containers().workspaces().create( + parent=container_path, + body={'name': 'API Changes', 'description': 'Automated setup'} +).execute() +workspace_path = workspace['path'] + +# 4. Create trigger +trigger = service.accounts().containers().workspaces().triggers().create( + parent=workspace_path, + body={'name': 'All Pages', 'type': 'PAGEVIEW'} +).execute() + +# 5. Create tag +tag = service.accounts().containers().workspaces().tags().create( + parent=workspace_path, + body={ + 'name': 'GA4 Config', + 'type': 'gaawe', + 'parameter': [{'key': 'measurementId', 'type': 'template', 'value': 'G-XXXXXX'}], + 'firingTriggerId': [trigger['triggerId']] + } +).execute() + +# 6. Create and publish version +version = service.accounts().containers().workspaces().create_version( + path=workspace_path, + body={'name': 'API Setup', 'notes': 'Automated GA4 configuration'} +).execute() + +published = service.accounts().containers().versions().publish( + path=version['containerVersion']['path'] +).execute() + +print(f"Published version: {published['containerVersion']['containerVersionId']}") +``` + +## Limitations + +- **No direct data layer access**: API manages configuration, not runtime data +- **No real-time testing**: Use Preview mode in GTM UI for testing +- **Version publishing is final**: Cannot unpublish, only publish new versions +- **Rate limits apply**: Be mindful of quota limits +- **Some features unavailable**: Certain GTM features may not be available via API + +## Resources + +- [GTM API Reference](https://developers.google.com/tag-platform/tag-manager/api/v2/reference) +- [GTM API Developer Guide](https://developers.google.com/tag-platform/tag-manager/api/v2/devguide) +- [OAuth 2.0 Documentation](https://developers.google.com/identity/protocols/oauth2) +- [Python Client Library](https://github.com/googleapis/google-api-python-client) +- [JavaScript Client Library](https://github.com/google/google-api-javascript-client) +- [Tag Dictionary Reference](https://developers.google.com/tag-platform/tag-manager/api/v2/tag-dictionary-reference) +- [Variable Dictionary Reference](https://developers.google.com/tag-platform/tag-manager/api/v2/variable-dictionary-reference) diff --git a/skills/gtm-best-practices/SKILL.md b/skills/gtm-best-practices/SKILL.md new file mode 100644 index 0000000..50a3a46 --- /dev/null +++ b/skills/gtm-best-practices/SKILL.md @@ -0,0 +1,150 @@ +--- +name: gtm-best-practices +description: Expert guidance for Google Tag Manager best practices including naming conventions, performance optimization, security, privacy compliance, container organization, and deployment strategies. Use when optimizing GTM performance, implementing security best practices, setting up naming conventions, auditing GTM containers, or ensuring privacy compliance. +--- + +# GTM Best Practices + +## Overview +This skill provides best practices for Google Tag Manager including naming conventions, performance optimization, security, privacy, and deployment strategies. + +## When to Use This Skill +Invoke this skill when: +- Optimizing GTM container performance +- Implementing security best practices +- Setting up naming conventions +- Auditing GTM containers +- Ensuring privacy compliance +- Planning deployment strategies +- Reviewing GTM implementations + +## Naming Conventions + +### Tags +Format: `[Platform] - [Type] - [Description]` +Examples: +- `GA4 - Event - Form Submit` +- `Google Ads - Conversion - Purchase` +- `FB - Pixel - Page View` + +### Triggers +Format: `[Event Type] - [Description]` +Examples: +- `Click - CTA Button` +- `Page View - Homepage` +- `Form Submit - Contact Form` + +### Variables +Format: `[Type] - [Description]` +Examples: +- `DL - User ID` +- `CJS - Format Price` +- `Cookie - Session ID` + +## Performance Optimization + +### Minimize Tag Load Time +- Use native tag templates instead of custom HTML +- Minimize custom JavaScript +- Implement async loading +- Configure appropriate timeouts +- Remove unused elements + +### Container Optimization +- Regular container audits +- Remove duplicate tags +- Consolidate similar tags +- Minimize trigger complexity +- Use tag sequencing wisely + +### Page Performance +- Defer non-critical tags +- Use async snippet +- Minimize container size +- Optimize custom HTML +- Monitor loading time + +## Security Best Practices + +### Tag Security +- Vet all custom HTML tags +- Review third-party templates +- Use template permissions +- Restrict custom JavaScript +- Validate input data + +### Data Privacy +- Avoid PII in data layer +- Implement consent mode +- Block tags based on consent +- Hash sensitive identifiers +- Review data collection policies + +### Access Control +- Use appropriate permission levels +- Review user access regularly +- Limit container admin access +- Audit permission changes +- Use workspaces for isolation + +## Deployment Strategies + +### Environment Setup +- Development environment for testing +- Staging environment for validation +- Production environment for live traffic +- Use container versions properly + +### Version Management +- Create versions with detailed notes +- Test thoroughly before publishing +- Maintain version history +- Document changes clearly +- Enable quick rollback capability + +### Publishing Workflow +1. Make changes in workspace +2. Test in Preview mode +3. Validate in development environment +4. Create version with notes +5. Publish to production +6. Monitor for issues +7. Document deployment + +## Container Organization + +### Folder Structure +- Group related tags by platform or purpose +- Use folders for triggers by type +- Organize variables by category +- Keep structure logical and consistent + +### Documentation +- Add notes to complex configurations +- Document custom JavaScript logic +- Maintain change log +- Share knowledge with team + +## Audit Checklist +- [ ] Naming conventions followed +- [ ] No unused tags, triggers, or variables +- [ ] Performance optimized +- [ ] Security reviewed +- [ ] Privacy compliance verified +- [ ] Proper version management +- [ ] Documentation complete + +## References +- **references/best-practices.md** - Naming conventions, organization, deployment strategies + +## Integration with Other Skills +## Integration with Other Skills +- **gtm-general** - General GTM guidance and concepts +- **gtm-setup** - Proper container setup and installation +- **gtm-tags** - Efficient tag configuration practices +- **gtm-triggers** - Trigger optimization and best practices +- **gtm-variables** - Variable naming and structure best practices +- **gtm-debugging** - Testing and validation workflows +- **gtm-datalayer** - Data layer naming and structure best practices +- **gtm-custom-templates** - Template development best practices +- **gtm-api** - API automation best practices diff --git a/skills/gtm-best-practices/references/best-practices.md b/skills/gtm-best-practices/references/best-practices.md new file mode 100644 index 0000000..ae51480 --- /dev/null +++ b/skills/gtm-best-practices/references/best-practices.md @@ -0,0 +1,597 @@ +# Google Tag Manager - Best Practices + +## Foundational Technical Constraints + +### JavaScript in Google Tag Manager (ES5 Requirement) + +**Critical:** Google Tag Manager primarily uses **ECMAScript 5 (ES5)** for JavaScript code, not modern ES6+. + +#### Where ES5 is Required + +1. **Custom JavaScript Variables** - Must use ES5 syntax only +2. **Custom HTML Tags** - Standard ` +``` + +**Caveats:** +- No syntax highlighting in GTM interface +- No error checking until runtime +- Harder to debug +- Only use when absolutely necessary + +### Regular Expressions in Google Tag Manager (RE2 Format) + +**Critical:** GTM uses **RE2 (GoLang regex)**, NOT standard JavaScript/PCRE regex. + +#### Key Differences from Standard Regex + +**NOT Supported in RE2:** +- ❌ Backreferences: `\1`, `\2`, `\g{name}` +- ❌ Lookahead assertions: `(?=...)`, `(?!...)` +- ❌ Lookbehind assertions: `(?<=...)`, `(?...)` +- ✅ Alternation: `|` +- ✅ Case-insensitive flag: `(?i)` + +#### Common RE2 Patterns for GTM + +**Match URLs:** + +```regex +# Exact match +^https://example\.com/page$ + +# Contains path +/checkout/ + +# Query parameter present +\?utm_source= + +# Multiple domains +^https://(www\.)?example\.(com|net) +``` + +**Match Page Paths:** + +```regex +# Product pages +^/products/[^/]+$ + +# Category with ID +^/category/\d+ + +# Blog posts +^/blog/[\w-]+$ +``` + +**Case-Insensitive Matching:** + +```regex +# Case-insensitive flag at start +(?i)^/checkout + +# Matches: /checkout, /Checkout, /CHECKOUT, etc. +``` + +**Character Classes:** + +```regex +# Digits only +^\d+$ + +# Alphanumeric +^[\w-]+$ + +# Specific characters +^[A-Z]{2,3}$ +``` + +#### Where Regex is Used in GTM + +1. **Trigger Conditions** - Page URL, Click URL, etc. +2. **Variable Formatting** - Regex Table variables +3. **Filter Conditions** - Various matching rules + +#### Full RE2 Syntax Reference + +Complete RE2 regex syntax is available in: +`.claude/skills/gtm-core/gtm-core/references/google-rew-regular-expressions-syntax.txt` + +## Naming Conventions + +### Tag Naming + +Use descriptive, consistent naming patterns: + +``` +[Platform] - [Type] - [Purpose] - [Trigger Condition] +``` + +**Examples:** +- `GA4 - Event - Purchase Complete - Thank You Page` +- `Google Ads - Conversion - Form Submit - Contact Form` +- `Facebook - Pixel - Page View - All Pages` +- `Custom - LinkedIn Insight - Page View - Career Pages` + +**Benefits:** +- Easy to find tags in large containers +- Clear purpose at a glance +- Sortable and filterable +- Easier collaboration + +### Trigger Naming + +``` +[Type] - [Condition] - [Description] +``` + +**Examples:** +- `Pageview - URL Contains - Checkout` +- `Click - Element ID - CTA Button` +- `Custom Event - purchase_complete` +- `Form Submit - Contact Form` + +### Variable Naming + +``` +[Type] - [Purpose] +``` + +**Examples:** +- `DLV - User ID` +- `JS - Page Category` +- `Constant - GA4 Measurement ID` +- `Regex Table - Page Type Mapping` + +**Prefixes:** +- `DLV` - Data Layer Variable +- `JS` - Custom JavaScript +- `Constant` - Constant value +- `1P Cookie` - First Party Cookie +- `URL` - URL variable + +### Folder Organization + +Group related items: + +``` +├── Analytics +│ ├── GA4 Tags +│ ├── GA4 Triggers +│ └── GA4 Variables +├── Advertising +│ ├── Google Ads +│ ├── Facebook +│ └── LinkedIn +├── Utilities +│ ├── Error Tracking +│ └── Performance Monitoring +└── Testing + └── Development Tags +``` + +## Container Organization + +### Workspace Management + +**Development Workflow:** + +1. Create workspace for each feature/change +2. Name workspace descriptively: `Add GA4 Ecommerce Tracking` +3. Work in isolation +4. Test thoroughly in Preview mode +5. Submit for review +6. Merge and publish + +**Workspace Best Practices:** + +- One feature per workspace +- Regular cleanup of abandoned workspaces +- Clear workspace descriptions +- Resolve conflicts promptly +- Use descriptive workspace names + +### Version Control + +**Version Naming:** + +``` +v[Major].[Minor] - [Description] +``` + +**Examples:** +- `v1.0 - Initial GTM Setup` +- `v1.1 - Add GA4 Ecommerce` +- `v2.0 - Major Restructure` +- `v2.1 - Fix Checkout Tracking` + +**Version Notes:** + +Include detailed notes: +``` +Changes: +- Added GA4 purchase event tracking +- Updated data layer structure for checkout +- Fixed duplicate page view issue + +Testing: +- Verified in dev environment +- Tested all checkout flows +- Confirmed data in GA4 DebugView + +Tags Added: +- GA4 - Event - Purchase Complete + +Tags Modified: +- GA4 - Event - Add to Cart (updated parameters) +``` + +### Multi-User Collaboration + +**Permissions:** + +- **No Access** - External users +- **Read** - Stakeholders, analysts (view only) +- **Edit** - Developers, marketers +- **Approve** - Senior developers, managers +- **Publish** - Select trusted users only + +**Collaboration Best Practices:** + +- Communicate workspace usage +- Regular team syncs +- Document changes thoroughly +- Use workspace approval workflow +- Maintain change log + +## Performance Optimization + +### Tag Load Order + +**Priority System:** + +Use tag firing priority (higher numbers fire first): + +``` +100 - Critical tags (error tracking, consent) +50 - Analytics tags (GA4, Adobe) +25 - Marketing tags (ads pixels) +10 - Third-party tags +0 - Default +``` + +**Tag Sequencing:** + +Set up tag firing order: +1. Tag that must fire first +2. Configure "Tag Sequencing" in advanced settings +3. Setup tag fires after cleanup tag completes + +### Minimize Custom HTML + +**Prefer:** +1. Built-in tags (GA4, Google Ads) +2. Community Gallery templates +3. Custom templates +4. Custom HTML (last resort) + +**Why:** +- Built-in tags are optimized +- Less maintenance +- Better performance +- Reduced security risk + +### Async vs Sync Loading + +**Asynchronous Loading (Default - Recommended):** + +```javascript +// GTM loads async by default + +``` + +**Benefits:** +- Non-blocking +- Better page performance +- Parallel loading + +**Synchronous (Use Sparingly):** + +Only when tag MUST execute before page renders. + +### Tag Timeout + +**Configure Timeout:** + +Admin → Container Settings → Tag Settings +- Default: 2000ms (2 seconds) +- Recommended: 3000-5000ms for complex tags + +**Prevent:** +- Tags blocking page indefinitely +- Poor user experience +- False abandonment metrics + +## Security Best Practices + +### Custom HTML Security + +**Review All Custom HTML:** + +```javascript +// ❌ DANGEROUS - Eval and dynamic script loading +eval(userInput); +document.write(''); + +// ✅ SAFE - Controlled execution +var allowedScripts = { + 'analytics': 'https://analytics.trusted.com/script.js' +}; +``` + +**Security Checklist:** +- [ ] No `eval()` with user input +- [ ] No `document.write()` with external sources +- [ ] Validate all external script sources +- [ ] Review third-party tag code +- [ ] Use CSP (Content Security Policy) headers + +### Data Layer Security + +**Never Push PII to Data Layer:** + +```javascript +// ❌ NEVER DO THIS +dataLayer.push({ + 'email': 'user@example.com', + 'phone': '+1234567890', + 'ssn': '123-45-6789' +}); + +// ✅ HASH OR PSEUDONYMIZE +dataLayer.push({ + 'userIdHash': sha256('user@example.com'), + 'hasPhone': true +}); +``` + +### Template Permissions + +**Custom Template Security:** + +Review template permissions: +- Access to APIs +- Access to global variables +- Access to local storage +- Network requests + +Grant minimum necessary permissions. + +## Quality Assurance + +### Testing Checklist + +**Before Publishing:** + +- [ ] Preview mode testing completed +- [ ] All triggers fire correctly +- [ ] Data layer variables populate +- [ ] Tags send expected data +- [ ] No console errors +- [ ] Cross-browser testing +- [ ] Mobile testing +- [ ] Edge case scenarios tested + +### Debug Workflow + +1. **Enable Preview Mode** +2. **Navigate to test page** +3. **Verify in Debug Panel:** + - Tags Fired + - Tags Not Fired + - Data Layer + - Variables +4. **Check receiving platform** (GA4, Google Ads, etc.) +5. **Test edge cases** + +### Automated Testing + +**Tag Manager API + Testing Framework:** + +```python +# Pseudo-code +def test_purchase_tag(): + trigger_purchase_event() + assert ga4_tag_fired() + assert correct_parameters_sent() +``` + +## Deployment Best Practices + +### Publishing Workflow + +1. **Development** → Test in development workspace +2. **Staging** → Test in staging environment +3. **Preview** → Final check in preview mode +4. **Publish** → Publish to live container +5. **Monitor** → Watch for errors/issues + +### Emergency Rollback + +**Quick Rollback:** + +Versions → Previous Version → Actions → Publish + +**Keep:** +- Last 3 working versions readily accessible +- Emergency contact list +- Rollback documentation + +### Production Deployment Checklist + +- [ ] Workspace approved by team lead +- [ ] All tests passing +- [ ] Change documented in version notes +- [ ] Stakeholders notified +- [ ] Monitoring in place +- [ ] Rollback plan ready + +## Maintenance + +### Regular Audits + +**Quarterly Review:** + +- Remove unused tags/triggers/variables +- Update deprecated features +- Review tag performance +- Check for duplicate tracking +- Verify naming consistency + +### Performance Monitoring + +**Monitor:** +- Page load time impact +- Tag load time +- Failed tags +- Timeout events +- Error rates + +**Tools:** +- Google Tag Assistant +- Chrome DevTools +- GTM Debug Panel +- GA4 DebugView + +### Version Cleanup + +**Retention Policy:** + +- Keep last 10-15 versions +- Archive old versions +- Document reason for major changes +- Maintain change history + +## Documentation + +### External Documentation + +**Maintain:** + +1. **GTM Implementation Guide** + - Container architecture + - Tag inventory + - Trigger mapping + - Variable dictionary + - Data layer specification + +2. **Change Log** + - Date of change + - Description + - Who made the change + - Reason for change + +3. **Troubleshooting Guide** + - Common issues + - Solutions + - Contact information + +### Version Notes Template + +```markdown +## Version X.X - [Description] + +### Changes Made +- List of changes + +### Tags Added +- Tag names and purposes + +### Tags Modified +- What changed and why + +### Tags Removed +- What was removed and why + +### Testing Completed +- Test scenarios +- Results + +### Known Issues +- Any limitations or issues +``` + +## Resources + +- [GTM Developer Documentation](https://developers.google.com/tag-platform/tag-manager) +- [GTM Help Center](https://support.google.com/tagmanager) +- [GTM Community](https://support.google.com/tagmanager/community) +- [RE2 Regex Syntax Reference](.claude/skills/gtm-core/gtm-core/references/google-rew-regular-expressions-syntax.txt) diff --git a/skills/gtm-custom-templates/SKILL.md b/skills/gtm-custom-templates/SKILL.md new file mode 100644 index 0000000..85f08d6 --- /dev/null +++ b/skills/gtm-custom-templates/SKILL.md @@ -0,0 +1,536 @@ +--- +name: gtm-custom-templates +description: Expert guidance for building Google Tag Manager custom templates using sandboxed JavaScript. Use when creating custom tag templates, custom variable templates, server-side client templates, converting regular JavaScript to sandboxed JS, debugging template code, writing template tests, publishing to the Community Template Gallery, working with .tpl template files, or using sandboxed JavaScript APIs like require(), sendPixel, injectScript, and other GTM template APIs. +--- + +# GTM Custom Templates Builder + +## Overview + +This skill provides comprehensive expertise for building Google Tag Manager custom templates using sandboxed JavaScript. Master template structure, sandboxed JavaScript APIs, permissions, testing, and publishing to the Community Template Gallery. + +## When to Use This Skill + +Invoke this skill when: +- Building a custom tag template for a third-party service +- Creating a custom variable template for complex data manipulation +- Developing server-side client templates +- Converting regular JavaScript to sandboxed JavaScript +- Debugging sandboxed JavaScript API errors +- Understanding template permissions and security +- Writing tests for custom templates +- Publishing templates to the Community Template Gallery +- Troubleshooting "undefined is not a function" errors in templates +- Implementing HTTP requests, pixel firing, or script injection in templates + +## Sandboxed JavaScript Fundamentals + +### What is Sandboxed JavaScript? + +Sandboxed JavaScript is a restricted subset of JavaScript used in GTM custom templates. It provides security by limiting what code can execute while offering safe APIs for common operations. + +**Key Differences from Regular JavaScript:** +- No direct DOM access +- No window/document object +- No eval() or Function() +- No arbitrary HTTP requests +- Must use require() to import APIs +- All operations require explicit permissions + +### Basic Template Structure + +```javascript +// Import required APIs +const sendPixel = require('sendPixel'); +const logToConsole = require('logToConsole'); +const encodeUriComponent = require('encodeUriComponent'); + +// Access template configuration +const endpoint = data.endpoint; +const eventName = data.eventName; + +// Execute template logic +const url = endpoint + '?event=' + encodeUriComponent(eventName); +sendPixel(url, data.gtmOnSuccess, data.gtmOnFailure); +``` + +## Building Tag Templates + +### Tag Template Workflow + +1. **Define Template Info** + - Template type (TAG) + - Display name and description + - Categories and branding + - Container contexts (WEB, SERVER, etc.) + +2. **Configure Template Parameters** + - Add fields for user configuration + - Set field types (TEXT, SELECT, CHECKBOX, etc.) + - Define validation rules + - Set default values + +3. **Write Sandboxed JavaScript Code** + - Require necessary APIs + - Access data from template fields + - Implement tag logic + - Call gtmOnSuccess/gtmOnFailure + +4. **Set Permissions** + - Configure required permissions + - Specify allowed URLs, cookies, etc. + - Minimize permission scope + +5. **Write Tests** + - Test successful execution + - Test error handling + - Test edge cases + +### Common Tag Template Patterns + +**Simple Pixel Tag:** +```javascript +const sendPixel = require('sendPixel'); +const encodeUriComponent = require('encodeUriComponent'); + +const pixelUrl = data.pixelUrl + + '?id=' + encodeUriComponent(data.pixelId) + + '&event=' + encodeUriComponent(data.eventName); + +sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure); +``` + +**HTTP Request Tag:** +```javascript +const sendHttpRequest = require('sendHttpRequest'); +const JSON = require('JSON'); + +const postBody = JSON.stringify({ + event: data.eventName, + userId: data.userId +}); + +const options = { + headers: {'Content-Type': 'application/json'}, + method: 'POST' +}; + +sendHttpRequest(data.endpoint, options, postBody) + .then(data.gtmOnSuccess) + .catch(data.gtmOnFailure); +``` + +**Script Injection Tag:** +```javascript +const injectScript = require('injectScript'); +const queryPermission = require('queryPermission'); + +const url = 'https://example.com/script.js'; + +if (queryPermission('inject_script', url)) { + injectScript(url, data.gtmOnSuccess, data.gtmOnFailure); +} else { + data.gtmOnFailure(); +} +``` + +## Building Variable Templates + +### Variable Template Basics + +Variable templates return a value that can be used in other GTM configurations. + +**Cookie Variable:** +```javascript +const getCookieValues = require('getCookieValues'); + +const cookieName = data.cookieName; +const cookies = getCookieValues(cookieName); + +if (cookies && cookies.length > 0) { + return cookies[0]; +} + +return data.defaultValue || ''; +``` + +**LocalStorage Variable:** +```javascript +const localStorage = require('localStorage'); + +const key = data.storageKey; +const value = localStorage.getItem(key); + +return value || data.defaultValue; +``` + +**Custom JavaScript Variable:** +```javascript +const makeTableMap = require('makeTableMap'); +const makeNumber = require('makeNumber'); + +// Convert table to lookup map +const lookupTable = makeTableMap(data.table, 'key', 'value'); + +// Perform lookup +const inputValue = data.inputVariable; +return lookupTable[inputValue] || data.defaultValue; +``` + +## Sandboxed JavaScript API Reference + +### Commonly Used APIs + +**Data Type Conversion:** +```javascript +const makeInteger = require('makeInteger'); +const makeNumber = require('makeNumber'); +const makeString = require('makeString'); + +const num = makeNumber('123.45'); // 123.45 +const int = makeInteger('123.45'); // 123 +const str = makeString(123); // '123' +``` + +**Network APIs:** +```javascript +const sendPixel = require('sendPixel'); +const sendHttpRequest = require('sendHttpRequest'); +const injectScript = require('injectScript'); + +// Pixel +sendPixel(url, onSuccess, onFailure); + +// HTTP Request +sendHttpRequest(url, options, body) + .then(onSuccess) + .catch(onFailure); + +// Script injection +injectScript(url, onSuccess, onFailure); +``` + +**Storage APIs:** +```javascript +const getCookieValues = require('getCookieValues'); +const setCookie = require('setCookie'); +const localStorage = require('localStorage'); + +// Cookies +const cookies = getCookieValues('cookieName'); +setCookie('name', 'value', { + domain: 'example.com', + path: '/', + 'max-age': 3600 +}); + +// LocalStorage +const value = localStorage.getItem('key'); +localStorage.setItem('key', 'value'); +``` + +**Utility APIs:** +```javascript +const encodeUri = require('encodeUri'); +const encodeUriComponent = require('encodeUriComponent'); +const decodeUri = require('decodeUri'); +const decodeUriComponent = require('decodeUriComponent'); +const JSON = require('JSON'); +const Math = require('Math'); +const Object = require('Object'); + +// URL encoding +const encoded = encodeUriComponent('hello world'); + +// JSON +const obj = JSON.parse('{"key":"value"}'); +const str = JSON.stringify({key: 'value'}); + +// Math +const random = Math.random(); +const rounded = Math.round(3.7); +``` + +**DOM APIs (Limited):** +```javascript +const callInWindow = require('callInWindow'); +const copyFromWindow = require('copyFromWindow'); + +// Call window function +callInWindow('functionName', arg1, arg2); + +// Copy from window +const gaData = copyFromWindow('ga'); +``` + +## Permissions System + +### Understanding Permissions + +Every API requires explicit permission configuration. Permissions define what URLs, cookies, or data the template can access. + +**sendPixel Permission:** +```json +{ + "instance": { + "key": {"publicId": "send_pixel", "versionId": "1"}, + "param": [{ + "key": "allowedUrls", + "value": {"type": 1, "string": "specific"}, + "list": [ + {"type": 1, "string": "https://example.com/*"} + ] + }] + } +} +``` + +**get_cookies Permission:** +```json +{ + "instance": { + "key": {"publicId": "get_cookies", "versionId": "1"}, + "param": [{ + "key": "cookieAccess", + "value": {"type": 1, "string": "specific"}, + "list": [ + {"type": 1, "string": "session_id"}, + {"type": 1, "string": "user_*"} + ] + }] + } +} +``` + +**Best Practice:** Use the most restrictive permissions possible. Avoid "any" when you can specify exact URLs or cookie names. + +## Template Testing + +### Writing Tests + +```javascript +// Test successful execution +scenarios: +- name: Tag fires successfully + code: |- + const mockData = { + endpoint: 'https://example.com/api', + eventName: 'test_event' + }; + + runCode(mockData); + + assertApi('gtmOnSuccess').wasCalled(); + assertApi('sendHttpRequest').wasCalledWith( + 'https://example.com/api', + assertThat.objectContaining({method: 'POST'}) + ); +``` + +### Test Mocking + +```javascript +// Mock API returns +mock('getCookieValues', (name) => { + if (name === 'session_id') { + return ['abc123']; + } + return []; +}); + +// Test with mock +const mockData = {cookieName: 'session_id'}; +let result = runCode(mockData); + +assertThat(result).isEqualTo('abc123'); +``` + +## Common Patterns and Solutions + +### Converting Regular JS to Sandboxed JS + +❌ **Regular JavaScript (won't work):** +```javascript +// Direct DOM access +document.getElementById('element'); + +// Direct window access +window.dataLayer.push({}); + +// XMLHttpRequest +const xhr = new XMLHttpRequest(); +``` + +✅ **Sandboxed JavaScript (will work):** +```javascript +// Use callInWindow +const callInWindow = require('callInWindow'); +callInWindow('dataLayer.push', {event: 'custom'}); + +// Use sendHttpRequest +const sendHttpRequest = require('sendHttpRequest'); +sendHttpRequest(url, {method: 'GET'}) + .then(data.gtmOnSuccess) + .catch(data.gtmOnFailure); +``` + +### Error Handling + +```javascript +const sendHttpRequest = require('sendHttpRequest'); +const logToConsole = require('logToConsole'); + +sendHttpRequest(data.endpoint) + .then(response => { + logToConsole('Success:', response); + data.gtmOnSuccess(); + }) + .catch(error => { + logToConsole('Error:', error); + data.gtmOnFailure(); + }); +``` + +### Data Validation + +```javascript +const makeNumber = require('makeNumber'); +const getType = require('getType'); + +// Validate input +if (getType(data.value) === 'undefined') { + data.gtmOnFailure(); + return; +} + +// Convert and validate +const numValue = makeNumber(data.value); +if (numValue === undefined) { + logToConsole('Invalid number'); + data.gtmOnFailure(); + return; +} + +// Continue with valid data +data.gtmOnSuccess(); +``` + +## Template Field Configuration + +### Common Field Types + +```javascript +// Text input +{ + "type": "TEXT", + "name": "apiKey", + "displayName": "API Key", + "simpleValueType": true, + "valueValidators": [ + {"type": "NON_EMPTY"}, + {"type": "REGEX", "args": ["^[a-zA-Z0-9]{32}$"]} + ] +} + +// Select dropdown +{ + "type": "SELECT", + "name": "eventType", + "displayName": "Event Type", + "selectItems": [ + {"value": "pageview", "displayValue": "Pageview"}, + {"value": "event", "displayValue": "Event"} + ], + "simpleValueType": true +} + +// Checkbox +{ + "type": "CHECKBOX", + "name": "enableDebug", + "checkboxText": "Enable Debug Logging", + "simpleValueType": true, + "defaultValue": false +} + +// Group +{ + "type": "GROUP", + "name": "advancedSettings", + "displayName": "Advanced Settings", + "groupStyle": "ZIPPY_CLOSED", + "subParams": [ + // Nested fields here + ] +} +``` + +## Publishing to Community Template Gallery + +1. **Complete Template Info** + - Descriptive name and description + - Appropriate categories + - Brand information + - Thumbnail image (optional) + +2. **Add Comprehensive Tests** + - Test all major code paths + - Test error handling + - Test edge cases + +3. **Document Template** + - Clear field descriptions + - Help text for complex fields + - Notes section with usage instructions + +4. **Submit for Review** + - Export template from GTM + - Submit to Community Template Gallery + - Address reviewer feedback + +## Template Boilerplates + +This skill includes ready-to-use template boilerplates: + +- **assets/tag-template-boilerplate.tpl** - Complete tag template structure +- **assets/variable-template-boilerplate.tpl** - Complete variable template structure + +Copy these files and customize for your specific use case. + +## References + +This skill includes comprehensive reference documentation: + +- **references/sandboxed-javascript-api.md** - Complete API reference +- **references/custom-templates-guide.md** - Simo Ahava's comprehensive guide +- **references/template-testing.md** - Testing documentation and patterns +- **references/template-examples.md** - Real-world template examples + +Search references for specific APIs: +```bash +grep -r "sendHttpRequest" references/ +grep -r "permissions" references/ +grep -r "testing" references/ +``` + +## Integration with Other Skills + +- **gtm-general** - Understanding GTM concepts and architecture +- **gtm-setup** - Testing templates in GTM containers +- **gtm-tags** - Understanding tag structure for tag templates +- **gtm-variables** - Understanding variable structure for variable templates +- **gtm-datalayer** - Templates that interact with data layer +- **gtm-debugging** - Testing and debugging custom templates +- **gtm-api** - Programmatic template management + +## Quick Reference + +**Import API:** `const apiName = require('apiName');` + +**Success/Failure:** Always call `data.gtmOnSuccess()` or `data.gtmOnFailure()` + +**Permissions:** Every API requires explicit permission configuration + +**Testing:** Use `runCode(mockData)` and assertions + +**Debugging:** Use `logToConsole()` with debug environment permission diff --git a/skills/gtm-custom-templates/assets/example_asset.txt b/skills/gtm-custom-templates/assets/example_asset.txt new file mode 100644 index 0000000..d0ac204 --- /dev/null +++ b/skills/gtm-custom-templates/assets/example_asset.txt @@ -0,0 +1,24 @@ +# Example Asset File + +This placeholder represents where asset files would be stored. +Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed. + +Asset files are NOT intended to be loaded into context, but rather used within +the output Claude produces. + +Example asset files from other skills: +- Brand guidelines: logo.png, slides_template.pptx +- Frontend builder: hello-world/ directory with HTML/React boilerplate +- Typography: custom-font.ttf, font-family.woff2 +- Data: sample_data.csv, test_dataset.json + +## Common Asset Types + +- Templates: .pptx, .docx, boilerplate directories +- Images: .png, .jpg, .svg, .gif +- Fonts: .ttf, .otf, .woff, .woff2 +- Boilerplate code: Project directories, starter files +- Icons: .ico, .svg +- Data files: .csv, .json, .xml, .yaml + +Note: This is a text placeholder. Actual assets can be any file type. diff --git a/skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl b/skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl new file mode 100644 index 0000000..f04db76 --- /dev/null +++ b/skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl @@ -0,0 +1,168 @@ +___INFO___ + +{ + "type": "TAG", + "id": "cvt_temp_public_id", + "version": 1, + "securityGroups": [], + "displayName": "My Custom Tag", + "categories": ["ANALYTICS", "MARKETING"], + "brand": { + "id": "brand_id", + "displayName": "My Brand", + "thumbnail": "" + }, + "description": "A custom tag template for...", + "containerContexts": [ + "WEB" + ] +} + + +___TEMPLATE_PARAMETERS___ + +[ + { + "type": "TEXT", + "name": "endpoint", + "displayName": "API Endpoint", + "simpleValueType": true, + "help": "Enter the API endpoint URL", + "valueValidators": [ + { + "type": "NON_EMPTY" + }, + { + "type": "REGEX", + "args": ["^https?://.*"] + } + ] + }, + { + "type": "TEXT", + "name": "eventName", + "displayName": "Event Name", + "simpleValueType": true, + "defaultValue": "custom_event" + }, + { + "type": "CHECKBOX", + "name": "debug", + "checkboxText": "Enable Debug Mode", + "simpleValueType": true, + "defaultValue": false + } +] + + +___SANDBOXED_JS_FOR_WEB_TEMPLATE___ + +// Require necessary APIs +const sendPixel = require('sendPixel'); +const logToConsole = require('logToConsole'); +const encodeUriComponent = require('encodeUriComponent'); + +// Access template data +const endpoint = data.endpoint; +const eventName = data.eventName; +const debug = data.debug; + +// Debug logging +if (debug) { + logToConsole('Custom Tag Firing', { + endpoint: endpoint, + eventName: eventName + }); +} + +// Build pixel URL +const pixelUrl = endpoint + '?event=' + encodeUriComponent(eventName); + +// Fire pixel +sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure); + + +___WEB_PERMISSIONS___ + +[ + { + "instance": { + "key": { + "publicId": "logging", + "versionId": "1" + }, + "param": [ + { + "key": "environments", + "value": { + "type": 1, + "string": "debug" + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + }, + { + "instance": { + "key": { + "publicId": "send_pixel", + "versionId": "1" + }, + "param": [ + { + "key": "allowedUrls", + "value": { + "type": 1, + "string": "any" + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + } +] + + +___TESTS___ + +scenarios: +- name: Tag fires successfully + code: |- + const mockData = { + endpoint: 'https://example.com/pixel', + eventName: 'test_event', + debug: false + }; + + // Call runCode to run the template's code. + runCode(mockData); + + // Verify that the tag finished successfully. + assertApi('gtmOnSuccess').wasCalled(); +- name: Debug mode logs to console + code: |- + const mockData = { + endpoint: 'https://example.com/pixel', + eventName: 'test_event', + debug: true + }; + + runCode(mockData); + + // Verify logging occurred + assertApi('logToConsole').wasCalledWith('Custom Tag Firing', { + endpoint: 'https://example.com/pixel', + eventName: 'test_event' + }); + + +___NOTES___ + +Created using Custom Template Boilerplate diff --git a/skills/gtm-custom-templates/assets/variable-template-boilerplate.tpl b/skills/gtm-custom-templates/assets/variable-template-boilerplate.tpl new file mode 100644 index 0000000..4da856a --- /dev/null +++ b/skills/gtm-custom-templates/assets/variable-template-boilerplate.tpl @@ -0,0 +1,133 @@ +___INFO___ + +{ + "type": "MACRO", + "id": "cvt_temp_public_id", + "version": 1, + "securityGroups": [], + "displayName": "My Custom Variable", + "categories": ["UTILITY"], + "brand": { + "id": "brand_id", + "displayName": "My Brand" + }, + "description": "A custom variable template for...", + "containerContexts": [ + "WEB" + ] +} + + +___TEMPLATE_PARAMETERS___ + +[ + { + "type": "TEXT", + "name": "cookieName", + "displayName": "Cookie Name", + "simpleValueType": true, + "help": "Name of the cookie to read", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "defaultValue", + "displayName": "Default Value", + "simpleValueType": true, + "help": "Value to return if cookie is not found" + } +] + + +___SANDBOXED_JS_FOR_WEB_TEMPLATE___ + +// Require necessary APIs +const getCookieValues = require('getCookieValues'); +const logToConsole = require('logToConsole'); + +// Access template data +const cookieName = data.cookieName; +const defaultValue = data.defaultValue; + +// Get cookie values +const cookies = getCookieValues(cookieName); + +// Return first cookie value or default +if (cookies && cookies.length > 0) { + return cookies[0]; +} + +return defaultValue; + + +___WEB_PERMISSIONS___ + +[ + { + "instance": { + "key": { + "publicId": "get_cookies", + "versionId": "1" + }, + "param": [ + { + "key": "cookieAccess", + "value": { + "type": 1, + "string": "any" + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + } +] + + +___TESTS___ + +scenarios: +- name: Returns cookie value when exists + code: |- + mock('getCookieValues', (name) => { + if (name === 'testCookie') { + return ['cookieValue']; + } + return []; + }); + + const mockData = { + cookieName: 'testCookie', + defaultValue: 'default' + }; + + let result = runCode(mockData); + + assertThat(result).isEqualTo('cookieValue'); + +- name: Returns default value when cookie does not exist + code: |- + mock('getCookieValues', (name) => { + return []; + }); + + const mockData = { + cookieName: 'nonExistentCookie', + defaultValue: 'defaultValue' + }; + + let result = runCode(mockData); + + assertThat(result).isEqualTo('defaultValue'); + + +___NOTES___ + +Created using Custom Variable Template Boilerplate diff --git a/skills/gtm-custom-templates/references/custom-templates-guide.md b/skills/gtm-custom-templates/references/custom-templates-guide.md new file mode 100644 index 0000000..0e7cb4f --- /dev/null +++ b/skills/gtm-custom-templates/references/custom-templates-guide.md @@ -0,0 +1,4051 @@ +# Custom Templates Guide For Google Tag Manager + +May 23, 2019 in Analytics \| +[Comments](https://www.simoahava.com/analytics/custom-templates-guide-for-google-tag-manager/#commento) + +> **Last updated 12 August 2020**: Added details about [server-side +> tagging](https://www.simoahava.com/analytics/server-side-tagging-google-tag-manager/). + +As I have finally managed to pick up my jaw from the floor, it’s now +time to tell you what’s got me so excited. [Google Tag +Manager](https://tagmanager.google.com/) recently released a new feature +called [**Custom +Templates**](https://developers.google.com/tag-manager/templates/). +Actually, it’s not fair to call it a *feature*. It’s a full-blown +paradigm shift in how we use Google Tag Manager. It’s a *suite* of +features designed to help brands, companies, and users create and share +their own custom JavaScript and HTML setups with ease, while taking care +that the code is optimized for delivery in the web browser. + +[](https://www.simoahava.com/images/2019/01/custom-templates-google-tag-manager.jpg "custom templates in google tag manager") + +**Custom Templates**, in short, are tag, variable, and Client templates +that **you** can create and configure. In other words, if you have a +cool idea for a tag (e.g. an analytics tracking tag for a vendor not +natively supported by GTM), a variable (e.g. a Custom JavaScript +variable that does something with a string), or a Client (e.g. a +server-side endpoint for some new analytics tool), you can now turn them +into reusable templates which can, in turn, be shared with other users +and containers via template export and import. You can also use the +[Community gallery](https://tagmanager.google.com/gallery/) to +distribute your templates. + +[](https://www.simoahava.com/images/2019/01/template-in-the-list.jpg "Example template") + +Templates use a customized, sandboxed version of JavaScript, which has +its own idiosyncratic vernacular that you must learn (with the help of +this guide, of course). The reason for this added complexity is that +with templates you can ensure that the code being executed is safe, +unintrusive, and optimized. + +Furthermore, templates you create will define certain **permissions** +that are required for the template code to be able to run. An additional +level of governance is provided by way of **policies** defined on the +web page itself where the template code might be run. The interplay +between these permissions and policies is a core feature of template +security. + +There are lots and lots of things to cover in this guide, so let’s just +get started. + +# Table of Contents + +# Table of Contents + + \[+show\] \[–hide\] + +- [How to read this guide](#how-to-read-this-guide) +- [Custom Templates in a nutshell](#custom-templates-in-a-nutshell) +- [Getting started](#getting-started) + - [Tag template walkthrough](#tag-template-walkthrough) + - [Step 1 - Create and set the details of the tag + template](#step-1---create-and-set-the-details-of-the-tag-template) + - [Step 2 - Create the UI](#step-2---create-the-ui) + - [Step 3 - Add some code](#step-3---add-some-code) + - [Step 4 - Modify permissions](#step-4---modify-permissions) + - [Step 5 - Preview and test](#step-5---preview-and-test) + - [Step 6 - Create tag and + preview](#step-6---create-tag-and-preview) + - [Step 7 - You’re done!](#step-7---youre-done) + - [Variable template walkthrough](#variable-template-walkthrough) + - [Step 1 - Create and set the details of the variable + template](#step-1---create-and-set-the-details-of-the-variable-template) + - [Step 2 - Create the UI](#step-2---create-the-ui-1) + - [Step 3 - Add some code](#step-3---add-some-code-1) + - [Step 4 - Modify permissions](#step-4---modify-permissions-1) + - [Step 5 - Create variable, add to tag, and + preview](#step-5---create-variable-add-to-tag-and-preview) + - [Step 7 - You’re done!](#step-7---youre-done-1) +- [Core concepts](#core-concepts) + - [Sandboxed JavaScript](#sandboxed-javascript) + - [ES6+ syntax](#es6-syntax) + - [Restrictions to what type of data you can + handle](#restrictions-to-what-type-of-data-you-can-handle) + - [Template APIs](#template-apis) + - [Server-side tagging](#server-side-tagging) + - [Policies](#policies) + - [Tests](#tests) +- [Official documentation](#official-documentation) +- [The editor](#the-editor) + - [Info view](#info-view) + - [Fields editor](#fields-editor) + - [Field configuration](#field-configuration) + - [Text input](#text-input) + - [Drop-down menu](#drop-down-menu) + - [Checkbox](#checkbox) + - [Radio buttons](#radio-buttons) + - [Simple table](#simple-table) + - [Param table](#param-table) + - [Group](#group) + - [Label](#label) + - [Utilizing APIs](#utilizing-apis) + - [The `data` object](#the-data-object) + - [Variable templates in the code + editor](#variable-templates-in-the-code-editor) + - [Tag templates in the code + editor](#tag-templates-in-the-code-editor) + - [Client templates in the code + editor](#client-templates-in-the-code-editor) + - [Permissions](#permissions) + - [Accesses Global Variables](#accesses-global-variables) + - [Accesses Local Storage](#accesses-local-storage) + - [Accesses Template Storage](#accesses-template-storage) + - [Reads Cookie Value(s)](#reads-cookie-values) + - [Reads Referrer URL](#reads-referrer-url) + - [Reads URL](#reads-url) + - [Injects Hidden Iframes](#injects-hidden-iframes) + - [Injects Scripts](#injects-scripts) + - [Logs To Console](#logs-to-console) + - [Reads Data Layer](#reads-data-layer) + - [Reads Document Character Set](#reads-document-character-set) + - [Reads Container Data](#reads-container-data) + - [Reads Event Metadata](#reads-event-metadata) + - [Reads Document Title](#reads-document-title) + - [Sends Pixels](#sends-pixels) + - [Sets A Cookie Value](#sets-a-cookie-value) + - [Tests](#tests-1) + - [Template preview](#template-preview) + - [Importing and exporting](#importing-and-exporting) + - [Advanced settings](#advanced-settings) + - [Running the template code](#running-the-template-code) +- [Templates in GTM’s Preview mode](#templates-in-gtms-preview-mode) +- [Field configuration reference](#field-configuration-reference) + - [“Edit row” dialog title](#edit-row-dialog-title) + - [“New row” button text](#new-row-button-text) + - [“New row” dialog title](#new-row-dialog-title) + - [“Not set” option](#not-set-option) + - [Allow empty strings](#allow-empty-strings) + - [Always in summary](#always-in-summary) + - [Clear on copy](#clear-on-copy) + - [Default value](#default-value) + - [Display line count](#display-line-count) + - [Display message when not set](#display-message-when-not-set) + - [Display name](#display-name) + - [Enabling conditions](#enabling-conditions) + - [Group style](#group-style) + - [Help text](#help-text) + - [Include variables](#include-variables) + - [Nested fields](#nested-fields) + - [Text as list](#text-as-list) + - [Validation rules](#validation-rules) + - [Value hint](#value-hint) + - [Value unit](#value-unit) +- [Policies reference](#policies-reference) + - [access_globals](#access_globals) + - [get_cookies](#get_cookies) + - [get_referrer](#get_referrer) + - [get_url](#get_url) + - [inject_hidden_iframe](#inject_hidden_iframe) + - [inject_script](#inject_script) + - [logging](#logging) + - [read_character_set](#read_character_set) + - [read_data_layer](#read_data_layer) + - [read_event_metadata](#read_event_metadata) + - [read_title](#read_title) + - [send_pixel](#send_pixel) + - [set_cookies](#set_cookies) +- [Final thoughts](#final-thoughts) + + X + + **The Simmer Newsletter** + +Subscribe to the [Simmer +newsletter](https://www.simoahava.com/newsletter/) to get the latest +news and content from Simo Ahava into your email inbox! + +## How to read this guide + +This is a long guide. It has to be - there’s so much about custom +templates that needs to be addressed in any document whose purpose is to +provide a comprehensive treatment of the subject matter. + +However, **don’t interpret my inability to write concise prose as +indicative of how complex custom templates are**. I can assure you - +they’re absolutely manageable by anyone who’s been using Google Tag +Manager for a while. + +This guide is a **reference**. Its purpose is to offer you documentation +to support your work with custom templates. + +Because of this, I want to suggest some different ways to approach this +guide. + +- **Everyone** should read the chapters [Custom Templates in a + nutshell](#custom-templates-in-a-nutshell) and [Core + concepts](#core-concepts). + +- I really recommend that **everyone** take a look at the two + walkthroughs in the [Getting started](#getting-started) chapter. + +- Keep the [Official documentation](#official-documentation) handy at + all times, particularly the API references for [web + templates](https://developers.google.com/tag-manager/templates/api) + and for [server-side + templates](https://developers.google.com/tag-manager/serverside/api). + +- When working with templates, the [Fields editor](#field-editor) (with + a deep-dive into [Field + configurations](#field-configuration-reference)) chapter should be + very useful - same as the one on [Permissions](#permissions). + +- If you’re a site admin, you might want to read through the [Policies + reference](#policies-reference) to get an idea of how you can further + restrict the execution of custom code on your site. + +- If you suffer from insomnia, start from the beginning and don’t stop + until you fall asleep. Should happen by the 10,000 word mark. + +Be sure to check out my other guide on how to [create a Facebook pixel +template](https://www.simoahava.com/analytics/create-facebook-pixel-custom-tag-template/) - +it should shed more light on how templates work. You can also check the +corresponding [video](https://youtu.be/5ESEtwq7fxc) if you prefer +watching rather than reading. + +You can also view all the custom templates I have created and/or +collected in [this GitHub +repository](https://github.com/sahava/GoogleTagManagerTemplates) and in +the [Templates](https://www.simoahava.com/custom-templates/) section of +this site. + +## Custom Templates in a nutshell + +Google Tag Manager’s Custom Templates offer a way to build a user +interface around the custom code you might want to run on the site using +Google Tag Manager. The user interface is what you’ve come accustomed to +when using GTM’s tags and variables. It comprises **text input fields**, +**settings**, **tables**, **labels**, **drop-down menus**, and so forth. + +[](https://www.simoahava.com/images/2019/01/tag-template-before-after.jpg "Tag template before and after") + +Obviously, the UI itself is already a huge asset. Being able to offer a +user interface in lieu of a complicated code block will minimize +problems arising from input errors, and will help keep the code stable. + +However, the templates have another, less apparent (but no less +impactful) function. They add layers of **protection** and **security** +to the code they abstract. Templates use a [custom **JavaScript +framework**](#sandboxed-javascript) which introduces a handful of +[APIs](#template-apis) (application programming interfaces) that you +must use if you want the code to actually do anything. + +This introduces a steep learning curve, because you can’t just +copy-paste code from [Stack Overflow](https://www.stackoverflow.com/) +any more. If you want to set a global `window` property, you need to use +[an API for +that](https://developers.google.com/tag-manager/templates/api#setinwindow). +If you want to log to console, you need to use [an API for +that](https://developers.google.com/tag-manager/templates/api#logtoconsole). +If you want to check the value of a cookie, guess what, you need to use +[an API for +that](https://developers.google.com/tag-manager/templates/api#getcookievalues). + +[](https://www.simoahava.com/images/2019/01/api-in-use.jpg "API in use") + +Basically any code that tries to access the global state of the page or +run any native JavaScript functions defined on the global level requires +an API call. + +So why this added complexity? Well, for one, these APIs make sure that +potentially dangerous and/or intrusive modifications to the global state +are done in a controlled manner. + +Whenever you want to use an API, you must `require()` it in the template +code. And when you introduce an API like that, the template +automatically generates a set of configurable +[permissions](#permissions) for that API call. + +[](https://www.simoahava.com/images/2019/01/api-permissions.jpg "API permissions") + +In a nutshell, templates encapsulate the logic you would otherwise +introduce with custom code. By introducing APIs with **permissions**, +the templates can be configured to work in a secure and easily managed +context. + +An added level of security is the introduction of +[**policies**](#policies-reference), where you as the site owner can add +some code to the web page itself, which can have additional levels of +control over how template permissions are resolved. + +For example, if I have a tag configured to send hits to some endpoint, I +can write a policy on the page that only allows pixel requests to one of +the many endpoints configured in the tag. + +``` +window.dataLayer = window.dataLayer || []; +function gtag(){dataLayer.push(arguments);} + +gtag('policy', 'send_pixel', function(container, policy, data) { + if (data.url !== 'https://snowplow.simoahava.com/i') { + throw('Invalid pixel endpoint!'); + } else { + return true; + } +}); +``` + +With that policy in place, the image request will only be executed if +the endpoint URL is `https://snowplow.simoahava.com/i`. Otherwise, the +tag will fail in an error, and you can see the error message in the +[**Errors** +tab](https://www.simoahava.com/analytics/new-errors-tab-preview-mode/) +of Preview mode. + +[](https://www.simoahava.com/images/2019/01/tag-errors.jpg "Tag errors") + +An additional perk of using templates is that you don’t have to add the +nasty `unsafe-eval` keyword to your [Content Security +Policy](https://www.simoahava.com/analytics/google-tag-manager-content-security-policy/). +Any code run through a template is compiled into JavaScript when the +container is written, and thus doesn’t require the use of `eval()`. + +Conversely, with Custom HTML tags and Custom JavaScript variables, the +code is written into a string which is then compiled with `eval()` at +runtime. This is a bad practice and requires a huge compromise in +security if using a Content Security Policy. + +I hope you can see the usefulness of Custom Templates. Imagine a library +of Custom Templates, where anyone can share their own work for others to +download and use in their containers. Smaller brands and companies could +finally get their tools and platforms out there for the masses using +Google Tag Manager. + +> **Update 2 October 2019**: You no longer have to imagine such a +> library as it now exists. Check out the [Community Template +> Gallery](https://tagmanager.google.com/gallery/) as well as my +> [introduction](https://www.simoahava.com/analytics/introducing-tag-manager-template-gallery/) +> to it. + +## Getting started + +Before exhausting you with all the details about custom templates (and +trust me, there’s a **lot** to digest in the feature), I want to start +by walking you through creating a **tag template** and a **variable +template**. We won’t use all the most complex features for this, but it +should serve as a nice intro to how custom templates work in Google Tag +Manager. + +Be sure to check out [this +guide](https://www.simoahava.com/gtm-tips/build-custom-universal-analytics-client-server-side-tagging/) +for a walkthrough of building a Client template for **server-side +tagging**. + +Don’t forget to check out my other article, which covers the creation of +a [Facebook pixel +template](https://www.simoahava.com/analytics/create-facebook-pixel-custom-tag-template/). +It should provide a more comprehensive (and more overwhelming) look at +how templates are created. + +### Tag template walkthrough + +In this walkthrough, we’ll go through how to create a simple **script +injection tag**. This is how many of the third-party vendors out there +want their scripts to be loaded. + +We’ll use a wonderful company called +[Conductrics](https://conductrics.com/) as an example. They have +developed a tool with which you can do A/B-testing and personalization, +using ML-driven targeting logic, dynamic goals, server-side and +client-side deployment options, and a whole host of other features to +help you answer those difficult business questions you have with +**data**. + +[](https://www.simoahava.com/images/2019/04/conductrics.jpg "Conductrics tag template") + +**Note!** You can of course replace the Conductrics-specific stuff with +some other vendor source URL, if you want. The steps you take in this +guide would still be identical. + +The tag template is simple, by design. Conductrics offers the option to +host the required JavaScript for you, so all you need to do is add the +``. + +Once you’re done, click **Save** and move on to the next chapter. + +#### Step 4 - Modify permissions + +The final thing to edit are the **permissions**. When you `require()` +APIs, you’ll also automatically enable their permission configurations +in the **Permissions** tab. See the [chapter on +permissions](#permissions) for more details on how these work. + +Anyway, since you are using the `logToConsole` and `injectScript` APIs, +their permissions are now available for editing. The `queryPermission` +and `require` APIs don’t have any permissions associated with them. + +Click to the **Permissions** tab and expand the two permissions you +find. + +[](https://www.simoahava.com/images/2019/04/permissions-expanded.jpg "Permissions expanded") + +The [Injects Scripts](#injects-scripts) permission is, surprise +surprise, for the `injectScript` API. It expects URL match patterns that +the value input by the user into the text field must match. + +Add the value `https://*.conductrics.com/` into the text field. + +This value basically means that the script URL injected in the page must +be a subdomain of `conductrics.com`, and it can have any path structure +(the `/` after the hostname acts as a wildcard). Thus it will match, for +example: + +- `https://conductrics.com/tracker` + +- `https://de.cdn-v3.conductrics.com/ac-aBcDeFgH/v3/agent-api/js/f-aBcDeFggg/dt-b1234567?apikey=api-w123456` + +The [Logs to Console](#logs-to-console) permission should be +self-explanatory. It governs how the `logToConsole` API works. You can +check the **Always log** option, because we manage logging to console in +the template code itself. + +Right now, the permissions should look like this: + +[](https://www.simoahava.com/images/2019/04/permission-settings-done.jpg "Permission settings done") + +Click **Save** to save the current template. + +#### Step 5 - Preview and test + +You can now click to edit the tag in the **Template Preview** window. +When you’ve added some text, you can click the **Test** button to see +what happens. + +You can also choose to show a **test page** from the template menu: + +[](https://www.simoahava.com/images/2019/04/template-preview.jpg "Template preview") + +Test different things: + +- Try with a random, non-URL string to see the warning about the missing + `https://`. + +- Try with `https://` but with a hostname that doesn’t contain + `conductrics.com` to see the error for incorrect URL. + +- Try with `https://domain.conductrics.com/loader` to see the error for + missing `apikey`. + +- Try with `https://domain.conductrics.com/loader?apikey=12345` to see + the code pass with flying colors. + +- Try with and without the **Debug** checkbox checked, and see how this + impacts what you see in the **Console**. + +[](https://www.simoahava.com/images/2019/04/console-logging.jpg "Console output") + +If you already have a [Conductrics +account](https://conductrics.com/contact/), you can test with a real +deployment URL to see the success message in the console. + +Finally, you can dig deep into the `iframe` of the **Test page** to find +your script tag there. + +[](https://www.simoahava.com/images/2019/04/script-in-test-page.jpg "Script in the test page") + +Once done testing to your satisfaction, you can do one final **Save** +and then close the template editor - you are ready to create your first +tag off of this template! + +#### Step 6 - Create tag and preview + +In the GTM UI, go to Tags and click **New**. In the overlay that opens, +find your new template and click it. + +[](https://www.simoahava.com/images/2019/04/create-new-tag.jpg "Create new tag") + +This screen should be familiar to you. The only difference to native +templates in GTM is the **Tag permissions** bar. Click it to preview +what permissions have been set for the template. + +[](https://www.simoahava.com/images/2019/04/permissions-in-the-template.jpg "Permissions in the template") + +Then, fill in the tag fields as you would with a regular template. For +testing purposes, just set it to fire on the **All Pages** trigger. + +You can try with invalid URLs to see the error messages. However, to +test how the tag actually works, use a correct (but ultimately invalid) +URL like `https://domain.conductrics.com/loader?apikey=12345`, and the +tag should look like this: + +[](https://www.simoahava.com/images/2019/04/first-conductrics-tag.jpg "First Conductrics tag") + +Then, go the **Preview mode** and enter your site. + +You should see the tag having fired, but in **Failed** state because the +endpoint returned a `404` error. + +[](https://www.simoahava.com/images/2019/04/conductrics-failed.jpg "Conductrics failed") + +Check out the [JavaScript +console](https://kb.mailster.co/how-can-i-open-the-browsers-console/), +too. You should see some relevant output there. + +[](https://www.simoahava.com/images/2019/04/javascript-console-output.jpg "JavaScript console output") + +Finally, since we’re **injecting a script**, you can drill into the page +elements to find it in the `` of the page. + +[](https://www.simoahava.com/images/2019/04/elements-panel.jpg "Elements panel") + +#### Step 7 - You’re done! + +**And that’s it!**. This was an extremely simple use case, and it might +seem like a really complicated way to go about it. However, remember +that you’re providing controls for **governance** and **responsible** +code injection here. + +The use of [permissions](#permissions), [sandboxed +JavaScript](#sandboxed-javascript), and, if you choose to use them, +[policies](#policies-reference) help you run a tight ship on what the +custom tags can and can’t do on the site. + +The template itself does a simple script injection, but I hope I +convinced you how powerful the UI editor can be, and we barely scratched +the surface! + +### Variable template walkthrough + +Variable templates differ from tag templates in that they only have a +singular purpose: to **return** a value. Optimally, they should **not** +have any *side effects*, which include e.g. setting variables in the +global scope, writing cookies, pushing to `dataLayer`, injecting +scripts, or firing pixels. + +In this example, we’ll create a simple variable which is intended to +work with the +[`hitCallback`](https://www.simoahava.com/gtm-tips/hitcallback-eventcallback/) +field of your Google Analytics tags. + +The idea is that the variable will return a function (`hitCallback` +always requires a function as its value), which when executed will write +a cookie into the browser. This cookie can then be used to check if less +than 30 minutes (the default expiration) have passed since the last +Google Analytics hit to GA, thus giving you a *very rough* estimate of +whether a GA session is currently active. + +So yes, we’re breaking the “no side effects” rule I literally *just* +mentioned, but this is a bit different. It’s not an uncontrollable side +effect, which it would be if it fired every single time the variable is +called. Instead, the functionality is restricted to the `hitCallback` +function, which is only executed **once**, when the Google Analytics +request has been dispatched. + +[](https://www.simoahava.com/images/2019/04/session-cookie-variable.jpg "Session cookie variable") + +Note, there are [more elegant +ways](https://www.simoahava.com/analytics/create-and-update-google-analytics-session-timeout-cookie/) +to handle this, but for the purposes of this guide I wanted to start off +with something simple. + +#### Step 1 - Create and set the details of the variable template + +Browse to **Templates**, and click **New** in the corner of the +**Variable Templates** area. + +[](https://www.simoahava.com/images/2019/04/session-cookie-new-template.jpg "New variable template") + +In the view that opens, make sure the **Info** tab is selected, and add +a **Name** and **Description** to the variable. + +[](https://www.simoahava.com/images/2019/04/session-cookie-description.jpg "Variable template details") + +As you can see, you can add HTML styling to the description field. The +text I wrote was this: + +``` +Use this variable to write (and update) a session cookie after each Google Analytics hit by using the hitCallback field in the GA tags. +``` + +Once done, click **Save** in the top right corner and you should see the +**Template Preview** area show the new template name. + +[](https://www.simoahava.com/images/2019/04/session-cookie-preview.jpg "Variable template preview") + +Next, let’s create the user interface for this template! + +#### Step 2 - Create the UI + +Click over to the **Fields** tab in the template editor. + +This template will comprise three [text input](#text-input) fields. One +for the cookie’s **maximum age**, one for the **cookie path**, and one +for the **cookie domain**. + +Click the **Add Field** button, and choose the Text Input field from the +overlay. + +[](https://www.simoahava.com/images/2019/04/session-cookie-text-input.jpg "Text input fields") + +Do this two more times, so that you end up with three text input fields. + +Name the first field `maxAge`, the second `cookiePath`, and the third +`cookieDomain`. + +[](https://www.simoahava.com/images/2019/04/session-cookie-three-fields.jpg "Three text fields") + +Expand the first field, `maxAge`, and click the **cogwheel** icon to +open its field configurations. Toggle on [**Always in +summary**](#always-in-summary), [**Default value**](#default-value), and +[**Display name**](#display-name). + +[](https://www.simoahava.com/images/2019/04/session-cookie-max-age-configurations.jpg "Max Age configurations") + +Edit the `maxAge` settings to be like this:n + +**Display name**: Maximum age in seconds +**Default value**: 1800 +**Always in summary**: Checked + +The **Display name** is what appears above the field in the editor. + +We’ll use a **Default value** of `1800` seconds (that’s 30 minutes), so +if the user doesn’t touch the field, that’s the value that will be used. + +**Always in summary** means that the field contents will show up when +the variable is opened in the UI but not in edit mode. It’s a +convenience thing, not vital in any way. + +[](https://www.simoahava.com/images/2019/04/session-cookie-max-age-ready.jpg "Max age ready") + +Next, click the **cogwheel** for the `cookiePath` field, and toggle the +same configurations on (**Display name**, **Default value**, and +**Always in summary**). + +Set them to these values: + +**Display name**: Cookie path +**Default value**: / +**Always in summary**: Checked + +Finally, click the **cogwheel** for the `cookieDomain` field, and toggle +the same conditions on, together with the **Help text** configuration. + +Set them to these values: + +**Display name**: Cookie domain +**Help text**: Set to ‘auto’ to write the cookie on the highest possible +domain name. +**Default value**: auto +**Always in summary**: Checked + +The fields should look like this: + +[](https://www.simoahava.com/images/2019/04/session-cookie-finished-fields.jpg "Session cookie finished fields") + +The [**Help text**](#help-text) configuration is pretty cool. If you +refresh the **Template Preview**, you’ll see how there’s a little +question mark next to the **Cookie domain** field. By hovering over it, +you’ll see the help text. + +[](https://www.simoahava.com/images/2019/04/session-cookie-template-preview.jpg "Session cookie template preview") + +Now you’re ready to add the code! + +#### Step 3 - Add some code + +Click the **Code** tab, and replace the contents with the following: + +``` +// Require the necessary APIs +const setCookie = require('setCookie'); + +// Build the options object from user input +const options = { + domain: data.cookieDomain, + 'max-age': data.maxAge, + path: data.path +}; + +// Return the hitCallback function +return () => { + // Set the cookie when the hit has been dispatched + setCookie('_ga_session', 'true', options); +}; +``` + +As you can see, it’s a **very** simple variable. It uses a single API, +[`setCookie`](https://developers.google.com/tag-manager/templates/api#setcookie) +(for obvious reasons), and it has a `return` statement at the end, which +returns a function wherever the variable is called. + +If you read the [`setCookie` +specification](https://developers.google.com/tag-manager/templates/api#setcookie), +you can see that it takes three arguments. + +`setCookie(name, value, options)` + +To make things a bit easier, we’re building the `options` object before +calling the method. The object has three properties, `domain` for the +domain name, `max-age` for the maximum age, and `path` for the cookie +path. + +The `setCookie` API has a nice feature where if you set the value of +`domain` to `'auto'`, it automatically finds the highest possible (i.e. +shortest) domain name it can use. Thus, if the variable is called on +`sub.domain.simoahava.com`, the API writes it on `simoahava.com`. + +As you can see, there are no validations or permission checks going on +here. After reading this guide, you’ll be able to extend the UI to do +the validations directly on the fields, or you can also check the field +values in code, falling back to some valid values in case the user input +is not perfect. + +Once you’ve added the code, make sure to **save** the template, and then +click on over to the **Permissions** tab of the editor. You’ll need to +make sure the template has permissions to write the cookie. + +#### Step 4 - Modify permissions + +In the **Permissions** tab, you should now see a permission for [Sets A +Cookie Value](#sets-a-cookie-value). It’s added automatically when you +run `require('setCookie')` in the template code. Pretty sweet! + +[](https://www.simoahava.com/images/2019/04/sets-a-cookie-value.jpg "Sets a cookie value") + +Expand the permission, and click the **+ Add allowed cookie** button. In +the overlay that opens, configure the cookie as follows: + +**Cookie name**: \_ga_session +**Domain**: \* +**Path**: \* +**Secure**: Any +**Session**: Any + +Click **Save** in the overlay when done. This is what you should see in +the permissions area: + +[](https://www.simoahava.com/images/2019/04/cookie-permissions.jpg "Cookie permissions") + +You’re done with the template! Remember to **Save** it one last time. + +There’s not much sense in previewing or testing it in the template +editor itself. The variable returns a **function**, so the only logical +place to test it is in an environment where the function is actually +executed in context. + +Thus, it’s time to create a variable from the template and add it to a +tag! + +#### Step 5 - Create variable, add to tag, and preview + +Click to **Variables** in the GTM UI, and click **New** in the +**User-Defined Variables** section. + +In the variable type picker, choose your newly created template from the +list. + +[](https://www.simoahava.com/images/2019/04/variable-picker.jpg "Variable picker") + +You can now configure the variable to your liking. The default settings +are more than fine for most cases, though. + +[](https://www.simoahava.com/images/2019/04/hitcallback-session-cookie.jpg "Hitcallback session cookie") + +Next, find your Google Analytics Page View tag (or create one if you +don’t have it), check its **Enable overriding settings in this tag**, +and browse to **More Settings** \> **Fields to Set**. Click **+Add +Field**. + +Set the **Field Name** to `hitCallback`, and **Value** to the variable +you just created. + +[](https://www.simoahava.com/images/2019/04/hitcallback-in-tag.jpg "Hitcallback in tag") + +Save the tag, and go to **Preview mode**. Then, load the page where the +Page View tag will fire. + +If all goes as planned, you should now find a new browser cookie named +`_ga_session`, with an expiration set to the number of seconds you set +in the **Maximum age in seconds** field. Easiest way to find your +browser cookies is to use the developer tools of your browser. + +In Chrome, press **CMD + OPT + I** (Mac) or **CTRL + SHIFT + I** +(Win/Linux) to open the developer tools. Then, activate the +**Application** tab, and select the **Cookies** option for your domain. +You should see the `_ga_session` cookie with value `true`, and an +expiration in the future (or in the past if you set its maximum age to +`0`). + +[](https://www.simoahava.com/images/2019/04/ga-session-cookie-dev-tools.jpg "GA Session cookie") + +If you see the cookie then congratulations, your variable template works +as intended! + +#### Step 7 - You’re done! + +The purpose of this guide was to walk you through the steps how to +create a variable template. + +Yes, we used an extremely simple example, but the idea was to get +familiarized with the *routine* of template creation, rather than jump +in the deep end with all the APIs, field configurations, and permissions +available. + +The main difference to tag templates is that the variable needs to +**return** something. Whatever happens before that final `return` +statement is up to you, but you might want to avoid too many side +effects unless you are absolutely certain they are only invoked in a +specific, predictable context (such as `hitCallback`). + +That’s it for the tutorial part! Now it’s time to explore with more +detail what custom templates are, how they work, and how they are such a +game-changer for tag management with GTM. + +But first, take a break. You’ve earned it. + +## Core concepts + +Welcome back! + +Before we get to the good stuff, let me go over some of the **core +concepts** of Custom Templates. These concepts will emerge and re-emerge +in much of the discussion below, and they are fundamental to +understanding what Custom Templates are and what they can do. + +### Sandboxed JavaScript + +Custom Templates are written with **JavaScript**. If you are unfamiliar +with the language but still aching to start working with templates, I +recommend taking a look at some [learning +materials](https://www.simoahava.com/analytics/10-javascript-concepts-for-web-analytics-implementation/#get-the-basics-right) +before trying your hand with the template code. Check out these (free) +learning portals, too: + +- [freeCodeCamp](https://www.freecodecamp.org) - Excellent and + comprehensive JavaScript and web technology tracks that span + everything from the very basics to creating stateful web services. + +- [Codecademy](https://www.codecademy.com) - Great tracks for all sorts + of programming languages and disciplines offered in a nice, + interactive way. + +A further complication is that Custom Templates don’t actually use just +any old browser JavaScript. They use a special, [**sandboxed** +version](https://developers.google.com/tag-manager/templates/sandboxed-javascript) +of the JavaScript language. + +Basically, any code you write in the code editor will be automatically +wrapped in a function that provides a single argument named `data`. + +``` +function(data) { + // Start of code editor code + + const log = require('logToConsole'); + const copyFromWindow = require('copyFromWindow'); + + const ga = copyFromWindow('GoogleAnalyticsObject'); + + if (typeof ga === 'undefined') { + log('Google Analytics not loaded!'); + } + + log(data.userInputText); + + data.gtmOnSuccess(); + + // End of code editor code +} +``` + +The `data` object is really important. Each field in the template will +be accessible as a property of the `data` object, and the value of that +property will be the result of the user’s interaction with the field. +The `data` object also has the `gtmOnSuccess()` and `gtmOnFailure()` +methods you must use to signal the tag’s successful completion or its +failure. Variable completion is signalled by a `return` statement. + +In addition to being automatically wrapped as a function body, the set +of JavaScript methods and accessors available to you is limited. +Basically, you will have no access to the global `window` object. This +includes things like `location`, `document` (and `document.cookie`), +`console`, and any constructors (such as `new Date()`. + +To access these global methods and properties, you’ll need to use the +**template APIs** exposed by the template editor. + +### ES6+ syntax + +ES6 (ECMAScript 6) is one of the most significant updates the JavaScript +standard, originally released in 2015, with prominent support in all the +major browsers. The code editor in GTM’s templates supports **some** ES6 +features. These features include: + +- [**`const` and `let` + keywords**](https://medium.com/front-end-weekly/es6-cool-stuffs-var-let-and-const-in-depth-24512e593268). + These two keywords are offered as alternatives to JavaScript’s `var` + keyword. The main difference is that `const` and `let` have + **block-level scope**, meaning they are scoped to the context of the + wrapping `{` and `}`, rather than the whole surrounding function + context as with `var`. Furthermore, there’s no [variable + hoisting](https://www.w3schools.com/js/js_hoisting.asp) - `const` and + `let` variables cannot be referenced before their declaration. + Finally, `const` variables cannot be reassigned. + +- [**Arrow + functions**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). + Arrow functions are a new way to write JavaScript function + expressions. It’s syntactically more compact, so pursuers of “elegant + code” rejoice. For example, + `var multiplyByTwo = function(a) { return a*2; };` becomes + `const multiplyByTwo = a => a*2;`. Unlike “regular” JavaScript + functions, there’s no `this` binding and no access to the `arguments` + object. + +You are of course free to use the “old” style of JavaScript. In the +templates’ code editor, I will try to adhere to ES6 syntax purely out of +habit, but also because using `const` and `let` actually has some +functional weight in the code, too. + +### Restrictions to what type of data you can handle + +To prevent users from circumventing template permissions and from +running code that breaks through the templates’ sandbox, Google Tag +Manager has a number of suppression mechanisms built in. + +1. **You can’t access `window` or `document` directly**. Access to the + global namespace is restricted to [template APIs](#template-apis). + Even if you tried something clever such as returning + `window.setTimeout` in a JavaScript variable, so that it’s + *technically* not in the code editor itself, the template editor + automatically parses all variables and user-based input brought into + the template before it is made accessible to the code editor. + +2. **You can’t pass non-plain objects**, such as instances created with + construtors, to the template code. It’s difficult to contain + security of these, because the prototype chain can be manipulated by + code outside the template, making the template itself vulnerable and + less secure. This basically means that things like + [`customTask`](https://www.simoahava.com/analytics/customtask-the-guide/) + will not be possible to do with variable templates for now, because + it requires access to a constructed, non-plain `model` object to + function. + +3. **You can’t access DOM elements**. This is kind of the same as (2), + because a DOM element represents a complex, non-plain object type. + Nevertheless, it means that you can’t access things like + `{{Click Element}}` in template code. + +[](https://www.simoahava.com/images/2019/05/template-restrictions.jpg "Template restrictions") + +In time, I’m certain the proliferation of APIs in the template will +solve many of the restrictions above, but first and foremost the point +of templates is to add a layer of security to prevent scripts outside +the template from messing with the code within. + +Another one of the idiosyncracies of the templates’ sandboxed JavaScript +is how function expressions exposed outside the template code are +encapsulated in a **wrapper** by GTM. + +The purpose of this wrapper is to make sure functions set in the window +or returned by the variable template can’t be used to circumvent +permissions set in the template. + +The wrapper itself is **almost always** inconsequential, because it +passes the parameters collected by the wrapper to the function created +in or handled by the template. However, if any one of the parameters +violates the restrictions listed above, the parameter is suppressed and +replaced with `undefined`. + +### Template APIs + +The most common global properties and methods you’ll need to use are +abstracted behind custom **APIs**. We’ve covered a number of API +examples in the preceding chapters. For example, there’s `logToConsole` +for logging items to the JavaScript console of the browser, and there’s +`copyFromWindow` for accessing global variables defined in the `window` +object. + +Each API comes with its own configurable set of permissions. The +`copyFromWindow` API, for example, requires that you define the global +keys the code has access to. As soon as you type the +`require('copyFromWindow')` statement and refresh the template, the +**Accesses Global Variables** permissions will become available in the +respective tab. + +[](https://www.simoahava.com/images/2019/01/copyfromwindow-permissions.jpg "Accesses global variables") + +As you can see, for these particular permissions you can define the keys +and whether the code has read and/or write and/or execution permissions +for them. You might have guessed now that **Access Global Variables** is +not limited to just `copyFromWindow` (which is read-only), but also +determines the usefulness of e.g. `setInWindow` (for writing/setting +global variables) and `callInWindow` (for calling global methods). + +The purpose of this sandboxed JavaScript is to give you a **safe and +secure** JavaScript environment to write and run your code in. The +interplay between your code and the permissions model is designed to +produce code that is as conservative as possible without compromising +the runtime logic of what you want to do with the code. + +This doesn’t mean that you can’t cause havoc with your custom template - +quite the contrary. However, the user interface guides you to make the +correct choices when choosing the APIs and the permissions thereof. +Hopefully the guide you are currently reading will further help you +understand how to make your code run in the most efficient and +responsible way possible. + +### Server-side tagging + +[Server-side +tagging](https://www.simoahava.com/analytics/server-side-tagging-google-tag-manager/) +has its [own set of +APIs](https://developers.google.com/tag-manager/serverside/api), which +mainly revolve around parsing HTTP requests and compiling HTTP +responses. The purpose of a Client is to read the incoming requests, +launch virtual container processes for tags and triggers, and then build +a response back to the source of the original request. + +This guide is mostly focused on Custom Templates in web containers, but +all the main features apply to Server-side tagging as well. + +### Policies + +The counterpart of permissions are **policies**. Policies give the site +owner the ability to block potentially hazardous code from running. It’s +somewhat similar to Google Tag Manager’s +[whitelist/blacklist](https://developers.google.com/tag-manager/devguide#restrict) +feature, except with policies you have extremely granular access to how +individual permissions are handled. + +Policies are basically `dataLayer` commands which specify what happens +when a Custom Template for which some permissions have been set tries to +execute on the page. + +If you don’t define any policies then the tag or variable will run its +course unimpeded. + +However, you can create a policy which is invoked whenever some specific +permission is accessed by the template. Let me show you an example: + +``` +const getCookie = require('getCookieValues'); +const log = require('logToConsole'); + +log(getCookie('_user_id')); +data.gtmOnSuccess(); +``` + +The template code above uses the `getCookieValues` API to log the value +of a cookie named `_user_id`. When you save that code, a new permission +appears in the editor, and you need to whitelist the cookie names the +code is allowed to access: + +[](https://www.simoahava.com/images/2019/01/whitelist-cookie-values.jpg "Whitelisted cookie values") + +Now, if you didn’t specify a policy, the code would always log the +cookie value into the console when the tag runs, because the `_user_id` +cookie has been whitelisted in the tag permissions. + +However, let’s say the page has the following code in the HTML: + +``` +window.dataLayer = window.dataLayer || []; +function gtag(){dataLayer.push(arguments);} + +gtag('policy', 'get_cookies', function(container, policy, data) { + if (data.name === '_user_id') { + throw('You are not allowed to query _user_id on this page!'); + } + return true; +}); +``` + +> Don’t be confused by the `gtag()` command - policies use the `gtag()` +> method, which is essentially a helper to push the arguments into +> `dataLayer`. You do **not** need to download the gtag.js library to be +> able to run policies on the site template. + +Now when the tag is run and the `getCookie('_user_id')` code is +executed, the policy you created will activate. This policy checks if +the requested cookie was named `_user_id`, and reacts by throwing an +error. Perhaps the developers have deemed it too risky to allow GTM to +query the `_user_id` cookie like this. + +You don’t **need** policies to work with Google Tag Manager’s Custom +Templates. However, given how popular Custom Templates are likely to +become, I do expect to see more and more policies defined, too. + +Be sure to check out the [policy reference](#policy-reference) at the +end of this guide for more details on how individual policies should be +set. + +### Tests + +**Tests** allow you to write *unit tests* for your template code +directly in the editor. The **tests** feature comprises a number of APIs +you can use to, for example, mock the data object and write assertions +against the compiled and executed template code. + +[Tests](https://www.simoahava.com/images/2019/11/template-test-example.jpg "Tests") + +For more information about how to write and run tests with custom +templates, see this guide: + +[Writing Tests For Custom Templates In Google Tag +Manager](https://www.simoahava.com/analytics/writing-tests-for-custom-templates-google-tag-manager/) + +## Official documentation + +When working with **Custom Templates**, you should have two documents at +hand: + +- [**Custom template APIs + reference**](https://developers.google.com/tag-manager/templates/api) - + This document introduces the various APIs you’ll need to use if you + want your code to run beyond the boundaries of the sandbox. + +- [**Custom template permissions + reference**](https://developers.google.com/tag-manager/templates/permissions) - + This reference lists the permissions the APIs require, and which are + used to create **policies** with which web pages can deploy an + additional layer of governance for running custom templates. + +[\*\*Custom template APIs for Server-side +tagging](https://developers.google.com/tag-manager/serverside/api) - +This document lists the APIs available for Client and tag templates in +[Server-side +tagging](https://www.simoahava.com/analytics/server-side-tagging-google-tag-manager/). + +Keep those documents open when working with Custom Templates. I won’t +repeat their contents in this guide, but I do refer to them where +necessary. + +## The editor + +Let’s jump right in! To start creating a new template, click the +**Templates** menu option. + +[](https://www.simoahava.com/images/2019/01/templates-menu.jpg "Templates in the menu") + +Next, click **New** in the right corner of the **Tag Templates** card. + +[](https://www.simoahava.com/images/2019/01/tag-templates-new.jpg "Tag templates new") + +What you see now is the **template** editor. There are several views and +menus here, so let’s start with a quick overview. + +[](https://www.simoahava.com/images/2019/01/editor-overview.jpg "Editor overview") + +**1** - The main navigation of the editor. If you have **Show advanced +settings** checked in the editor menu, you’ll also see the **Notes** tab +here. + +**2** - The editor view for the selected tab. If you have **Show +advanced settings** checked in the editor menu, you might see extra +options here. + +**3** - Template Preview shows what the template will look like when +saved. If you’ve made changes to the template, you’ll see a **Refresh** +link here, which will update the preview when clicked. + +**4** - The Console will log information about template performance when +you **Test** the template. Additionally, any calls to the `logging` API +will be output here, too. + +If you select **Show Test Page** from the editor menu, you’ll see the +**Test Page** window between the Template Preview and Console. + +**5** - Click Test to execute the template code with the settings you +have entered in the preview. If you’ve chosen **Show Test Page** from +the editor menu, then any code that modifies the page will impact what +the Test Page shows. + +**6** - Save the template. The template cannot be saved if the code has +syntax errors or invalid JavaScript. You need to fix the errors before +saving. Note that the template can be saved even if you have incorrect +permissions for the APIs you use. + +**7** - The editor menu. The editor menu has options for managing the +template and for customizing the editor. + +The editor for **tag templates** and **variable templates** is largely +the same. The main difference is the absence of an icon/logo selector in +the [info view](#info-view) and how the code editor is utilized. With +tags, the code editor needs `data.gtmOnSuccess()` for successful +execution and `data.gtmOnFailure()` (if necessary) for a failure. +Variables, on the other hand, don’t use this. You just need a `return` +statement that returns whatever the variable is supposed to return. + +### Info view + +The **Info view** is where you’ll customize what the template looks +like. Verbosity is not a sin - try to be as clear and descriptive in the +template name and description as possible. + +[](https://www.simoahava.com/images/2019/01/info-view.jpg "Info view") + +The **Name** of the template is what appears in the tag/variable +selection menu as well as in the template itself when you create a new +instance from it. + +The **Description** will appear in the tag/variable selection menu below +the template name. + +Tag templates allow you to choose an **image** for the template. This +image will appear in the tag selection menu and the template itself. + +[](https://www.simoahava.com/images/2019/01/tag-template-menu.jpg "Tag template menu") + +At the bottom of the Info view is the checkbox for approving the terms +of service for the **Community Template Gallery**. You only need to +check this if you want to submit your template to the +[gallery](https://tagmanager.google.com/gallery/). + +[](https://www.simoahava.com/images/2019/10/terms-of-service-template.jpg "TOS template") + +If you have **Show advanced settings** selected, you’ll also see the +template **version** as well as the **contexts** in which the template +works. + +You’ll also see a **Source** toggle in the top of the view. By clicking +the toggle, you’ll see the JSON representation of the template info, +which you can edit (if you want to change the context, for example). + +[](https://www.simoahava.com/images/2019/01/source-toggle-info.jpg "Source toggle for info") + +### Fields editor + +The **Fields editor** is where you’ll spend a lot of your time. You use +this editor to establish what the tag or variable user interface will +look like, and how individual fields interact with each other. + +For each field you create, you need to specify (at least) a name with +which you can refer to the field value in the code editor. The field +name must be **unique**, since all fields (even those nested within +other fields) will be directly accessible using the field name as a +property of the `data` object. For example, to access the value of a +field whose name is `gaTrackingId`, you’d use this in the code editor: + +``` +const ua = data.gaTrackingId; +log(ua); // logs UA-12345-1 or whatever the user set the value of the field to. +``` + +Furthermore, each field has a **field configuration** that you’ll use to +establish how the field functions in the user interface of the template +itself. + +[](https://www.simoahava.com/images/2019/01/field-editor.jpg "Field editor view") + +Below the **field name** are the options and settings for the field. +You’ll see more of these as you add additional **configurations**. + +In the top right corner of each field row you’ll see a **trash can** for +deleting the field. + +There’s also a **cogwheel** which opens the **field configuration** +options. + +The **more options** menu opens options for manipulating the positioning +of the field (see screenshot above), and the **caret** at the very right +lets you collapse and expand the field in the editor. + +If you have **Show advanced settings** enabled, you can toggle the +**Source** toggle in the top corner of the view to see a JSON +representation of the fields. + +[](https://www.simoahava.com/images/2019/01/source-toggle-fields.jpg "Source toggle for fields") + +#### Field configuration + +Whenever you add a field in the editor, you have the option of +configuring field-specific rules and settings by clicking the +**cogwheel** icon next to the field. + +[](https://www.simoahava.com/images/2019/01/field-configuration-intro.jpg "Field configuration") + +Depending on the field you opened the configuration for, you’ll see a +set of toggles that you can toggle on or off. Some toggles are on by +default, again depending on the field you are configuring. + +[](https://www.simoahava.com/images/2019/01/text-input-configuration.jpg "Text input configuration") + +As you read through the field descriptions below, I’ve added the +configuration options for each field. Furthermore, at the end of this +guide there’s a [field configuration +reference](#field-configuration-reference) which lists all the possible +configuration options in more detail. + +Note that some fields can be **nested**, and some fields actually +include nested fields by default (e.g. the [Param table](#param-table) +field). In these cases, the nested fields are treated as their own +fields with their own configurations. The only difference between an +isolated and nested field is that the latter is subservient to the field +configurations of its parent. For example, if the parent is disabled due +to enabling conditions not validating, the nested fields will be +disabled, too. + +#### Text input + +##### Description + +The **Text input** field is a simple input box to which the user can +write text. + +[](https://www.simoahava.com/images/2019/01/text-input-1.jpg "Text input field in the editor") +Text input field in the editor + +[](https://www.simoahava.com/images/2019/01/text-input-2.jpg "Text input field in the UI") +Text input field in the UI + +##### Code editor output + +The property `data.fieldName` will resolve to whatever the user typed in +the field, or whatever a GTM variable used in the field resolves to. + +``` +// Using the example from the screenshot above +const textFieldValue = data.textToWriteToConsole; +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [Allow empty strings](#allow-empty-strings) - Convert and unfilled + field into an empty string. OFF by default. +- [Always in summary](#always-in-summary) - Show the field and its + current value in the summary view of the tag or variable. OFF by + default. +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display line count](#display-line-count) - Line count of greater than + 1 turns the field into a text area. OFF by default. +- [Display message when not set](#display-message-when-not-set) - When + the field is untouched, show this text in the summary view. OFF by + default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Text as list](#text-as-list) - When the text field is a text area + (see **Display line count** above), store the value of the field as an + array, where each item represents a row of input. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. +- [Value hint](#value-hint) - Text shown as a placeholder help text in + the field before the user starts editing it. OFF by default. +- [Value unit](#value-unit) - Text shown next to the field. Useful to + specify a format or type, for example. OFF by default. + +#### Drop-down menu + +##### Description + +The **Drop-down menu** field provides a menu where only **a single item +can be selected**. It’s often a combination of predefined items (such as +`True` and `False`), and all the Google Tag Manager variables in the +container. + +[](https://www.simoahava.com/images/2019/01/dropdown-menu-1.jpg "Drop-down menu in the editor") +Drop-down menu in the editor + +[](https://www.simoahava.com/images/2019/01/drop-down-menu-2.jpg "Drop-down menu in the UI") +Drop-down menu in the UI + +##### Code editor output + +The `data.fieldName` property will resolve to the value of the item +selected from the drop-down menu. If the item was one of those you +defined in the template editor, then what you wrote in the Value field +is what the property resolves to. If the user selected a GTM variable, +then the value returned by that variable is what the property resolves +to. + +``` +const dropDownFieldValue = data.dropDownMenu1; +log(dropDownFieldValue); // logs true if an item whose Value is true is selected. +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [“Not set” option](#not-set-option) - Show a placeholder value in the + menu before the user has selected anything. If menu is left untouched, + the field returns an empty string. OFF by default. +- [Always in summary](#always-in-summary) - Show the field and its + current value in the summary view of the tag or variable. OFF by + default. +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Include variables](#include-variables) - Check this box if you want + to include all GTM variables in the drop-down menu. **ON by default**. +- [Nested fields](#nested-fields) - Nested fields are useful if you want + to show fields related to a specific value of the parent field only + under certain conditions, for example. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. +- [Value unit](#value-unit) - Text shown next to the field. Useful to + specify a format or type, for example. OFF by default. + +#### Checkbox + +##### Description + +The **Checkbox** field should be self-explanatory. The checkbox doesn’t +have a display name by default. Instead, text you type in the **Checkbox +text** field will be shown next to the box in the UI. + +[](https://www.simoahava.com/images/2019/01/checkbox-1.jpg "Checkbox in the editor") +Checkbox in the editor + +[](https://www.simoahava.com/images/2019/01/checkbox-2.jpg "Checkbox in the UI") +Checkbox in the UI + +##### Code editor output + +The `data.fieldName` property resolves to `true` if the checkbox was +checked and `false` if not. + +``` +const checkBoxValue = data.useDataLayer; +log(checkBoxValue); // true +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [Always in summary](#always-in-summary) - Show the field and its + current value in the summary view of the tag or variable. OFF by + default. +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. OFF by default. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Nested fields](#nested-fields) - Nested fields are useful if you want + to show fields related to a specific value of the parent field only + under certain conditions, for example. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. + +#### Radio buttons + +##### Description + +You can add one or more radio buttons into a single radio button group. +The radio button group is treated as a single field. Each radio button +has a name (displayed next to the button) and a value (what is returned +if the button is selected). The user can only select one button from the +group. You can expand **advanced settings** to add a help text and to +add nested fields under each radio button selection. + +[](https://www.simoahava.com/images/2019/01/radio-buttons-1.jpg "Radio buttons in the editor") +Radio buttons in the editor + +[](https://www.simoahava.com/images/2019/01/radio-buttons-2.jpg "Radio buttons in the UI") +Radio buttons in the UI + +##### Code editor output + +The `data.fieldName` property, where `fieldName` is the name of the +entire radio button group, will resolve to the value of the selected +radio button. + +``` +const selectedButton = data.gaEventType; +if (selectedButton === 'pageView') { + const gaId = data.trackingId; + // Do something with gaId +} +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Nested fields](#nested-fields) - Nested fields are useful if you want + to show fields related to a specific value of the parent field only + under certain conditions, for example. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. + +#### Simple table + +##### Description + +With a **simple table**, you can define columns (either [text +input](#text-input) fields or [drop-down menus](#drop-down-menu)), and +the users can add and remove rows to and from the table as they wish. + +You can specify that all values in a column must be unique (i.e. the +user can’t add multiple rows with the same value in such a column), and +you can add things like default values and validation rules to each +column, once you have selected to **Show advanced settings** for the +column. + +[](https://www.simoahava.com/images/2019/01/simple-table-1.jpg "Simple table in the editor") +Simple table in the editor + +[](https://www.simoahava.com/images/2019/01/simple-table-2.jpg "Simple table in the UI") +Simple table in the UI + +##### Code editor output + +The `data.fieldName` property will resolve to an array of objects, where +each object represents a row the user has added in the UI. Furthermore, +each row object will have key-value pairs for every column, where the +key is the column name and the value is the value of the input (the +selection value if a drop-down menu or the input text if a text field). + +``` +log(data.cookieSettings); +/* Logs: +[ + {"cookieOption":"cookieDomain","optionValue":"simoahava.com"}, + {"cookieOption":"cookieName","optionValue":"myCookie"} +] +*/ +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [“New row” button text](#new-row-button-text) - Change the value of + the “Add row” button text. OFF by default. +- [Always in summary](#always-in-summary) - Show the field and its + current value in the summary view of the tag or variable. OFF by + default. +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display message when not set](#display-message-when-not-set) - When + the field is untouched, show this text in the summary view. OFF by + default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. + +#### Param table + +##### Description + +A **Param table** is a more complicated table. Instead of being able to +edit individual column values in the table itself, a param table +requires you to input all the values of the row in a special overlay +menu that pops up when you click to add a row. The individual columns +can be any of the supported field types, and thus you can add far more +complexity into the table than you could with a regular [simple +table](#simple-table) field. + +[](https://www.simoahava.com/images/2019/01/param-table-1.jpg "Param table in the editor") +Param table in the editor + +[](https://www.simoahava.com/images/2019/01/param-table-2.jpg "Param table in the UI") +Param table in the UI + +##### Code editor output + +Similar to the [simple table](#simple-table), the `data.fieldName` +property resolves to an array of objects, where each object represents a +row in the table. Each row object has key-value pairs for each column, +and the value is whatever the column field returns. For example, the +screenshot above would render as: + +``` +log(data.userSelection); +/* Logs: +[ + {"genderSelect": "male", "nameInput": "Simo Ahava"}, + {"genderSelect": "female", "nameInput": "Simona Ahava"} +] +*/ +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [“Edit row” dialog title](#edit-row-dialog-title) - You can change + what the heading of the “Edit row” overlay is. OFF by default. +- [“New row” button text](#new-row-button-text) - Change the value of + the “Add row” button text. OFF by default. +- [“New row” dialog title](#new-row-dialog-title) - You can change what + the heading text is in the overlay you see when adding a new row. OFF + by default. +- [Always in summary](#always-in-summary) - Show the field and its + current value in the summary view of the tag or variable. OFF by + default. +- [Clear on copy](#clear-on-copy) - Prevent filled field values from + being copied when a copy is made of the tag or variable. OFF by + default. +- [Default value](#default-value) - The value of the field until the + user decides to change it. OFF by default. +- [Display message when not set](#display-message-when-not-set) - When + the field is untouched, show this text in the summary view. OFF by + default. +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. +- [Validation rules](#validation-rules)- One or more rules against which + the field must validate before the user can save the tag or variable. + OFF by default. + +#### Group + +##### Description + +A **Group** is simply a logical way to group different fields together. +The main benefit is that it offers you a **Group style** selection. + +- **Simple section** - This style simply shows the nested fields in the + group without any collapsing. + +- **Collapsible section – Open** - This style shows the nested fields in + a collapsible section which is open initially. + +- **Collapsible section – Closed** - This style shows the nested fields + in a collapsible section which is collapsed initially. + +- **Collapsible section – Open if not default** - This style shows the + nested fields in a collapsible section which is open if the nested + field(s) don’t have default values (i.e. the user has changed the + value of the fields). + +A Group is useful if you want to section a set of fields separately, +because you can also control the entire groups visibility with the +**Enabling condition** field configuration. + +[](https://www.simoahava.com/images/2019/01/more-settings-1.jpg "Collapsible group in the editor") +Collapsible group in the editor + +[](https://www.simoahava.com/images/2019/01/more-settings-2.jpg "Collapsible group in the UI") +Collapsible group in the UI + +##### Code editor output + +The **Group** doesn’t bring anything extra to the code editor. The +nested fields are accessed directly as properties of the `data` object - +the group itself is not present in the object in any way. + +``` +log(data); +/* Logs: +[ + {"cookieSettings": [{"cookieOption": "cookieDomain", "optionValue": "auto"}]}, + {"trackerName": "_ga_tracker"} +] +*/ +``` + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. +- [Group style](#group-style) - How the group section is rendered in the + GTM UI (simple vs. collapsed vs. open). **ON by default**. +- [Help text](#help-text) - Add text to a question mark tooltip shown + next to the field. OFF by default. + +#### Label + +##### Description + +The **Label** field is extremely simple. It’s just text that you show in +the GTM UI when the instance is opened. The **Display name** +configuration determines the text that is shown. Additionally, a Label +field has only one other configuration: an **Enabling condition** which +you can use to conditionally show the text. + +[](https://www.simoahava.com/images/2019/01/label-1.jpg "Label in the editor") +Label in the editor + +[](https://www.simoahava.com/images/2019/01/label-2.jpg "Label in the UI") +Label in the UI + +##### Code editor output + +There is no way to access the label in the code editor. + +##### Available field configurations + +> Click the configuration name to jump to details about the +> configuration in the reference at the end of this guide. + +- [Display name](#display-name) - The label of the field shown in the + GTM UI. **ON by default**. +- [Enabling conditions](#enabling-conditions) - Establish conditions + (based on other field inputs) for showing this particular field. OFF + by default. + +#### Utilizing APIs + +**Template APIs** are methods in the Google Tag Manager sandboxed +JavaScript which let you invoke commonly used utilities of the browser. +GTM restricts direct access to global methods to improve the stability +and reliability of the code, and to optimize the way the code is +delivered in the browser. + +When working with the code editor, remember to always have the [API +reference](https://developers.google.com/tag-manager/templates/api) +document open in your browser. + +Because the official documentation is pretty thorough, I won’t do an +exhaustive overview of all the APIs. Instead, I’ll point out **quirks** +or **things you should know** about some of the APIs, listed below in +their separate chapters. + +##### The `require` API + +To invoke a template API, you must first **require** it in the editor. +If you’ve been working with [Node.js](https://nodejs.org/en/), you’ll +recognize the `require()` method as a way to include JavaScript +**modules** into your code. + +In GTM templates, `require()` is used to gain access to template APIs, +so the functionality is similar even if quite a bit more restricted, as +the list of APIs you can integrate is limited. + +To utilize an API, you simply run `const method = require(name);`, where +`name` is the name of the API you want to use, and `method` is the name +of the variable to which you will locally scope the API function. + +For example, to make it possible to log to the console and to set +cookies in the template code, you would need to require the respective +APIs: + +``` +const log = require('logToConsole'); +const setCookie = require('setCookie'); +``` + +When you require an API that is governed by a set of **permissions**, +those permissions will automatically appear in the +[Permissions](#permissions) tab of the template UI. When you use the +API, you need to make sure that the permissions allow you to perform the +tasks you are trying to perform. + +##### The `queryPermission` API + +Especially if you’ve written the template for public use, you might want +to utilize the `queryPermission` API to wrap your code in a validator +that only runs if the required permissions have been set. + +For example, if you want to make sure you can actually set some cookie, +you might want to use this type of syntax: + +``` +const queryPermission = require('queryPermission'); +const setCookie = require('setCookie'); + +const options = { + domain: 'www.simoahava.com' +}; + +// Check whether it's possible to write the cookie before writing it +if (queryPermission('set_cookies', '_gaClientId', options)) { + setCookie('_gaClientId', 'abc123', options); +} +``` + +The `setCookie()` API code is only run if the permissions allow you to +write a cookie named `_gaClientId` on the domain `www.simoahava.com`. +This is a good way to avoid your tag running into errors. + +##### The `copyFromDataLayer` API + +[This +API](https://developers.google.com/tag-manager/templates/api/#copyfromdatalayer) +is relatively straightforward - it fetches the item from GTM’s data +model that you requested. For example, to fetch the current value for +`gtm.elementUrl`, you’d run: + +``` +const copyFromDataLayer = require('copyFromDataLayer'); +const clickUrl = copyFromDataLayer('gtm.elementUrl'); +``` + +This code would fetch the value from the data model at the time that the +tag was run. + +However, there’s a very important **catch** here. + +If the template code fetching the `dataLayer` value is run +**asynchronously** (e.g. with the [`callLater` +API](https://developers.google.com/tag-manager/templates/api/#calllater)), +or if the tag built from the template is part of a [**tag +sequence**](https://www.simoahava.com/analytics/understanding-tag-sequencing-in-google-tag-manager/), +the value fetched from `dataLayer` will be **whatever is currently +stored in the data model**. In all other scenarios, the code is run +synchronously, relative to the `dataLayer.push()`, so the value returned +by `copyFromDataLayer` will reflect what was included in the pushed +object. + +To illustrate, consider the following code executed on the site: + +``` +window.dataLayer.push({ + event: 'fire', + key: 'value' +}); + +window.dataLayer.push({ + event: 'fire', + key: 'otherValue' +}); +``` + +These two `dataLayer.push()` calls are run one after the other. If +you’ve built a tag that fires on the “fire” event, and the template for +that tag uses `copyFromDataLayer` to fetch the value for `key`, then in +most cases it will always return whatever the value of `key` was during +the trigger push. + +In other words, the first time the tag fires, `key` will be set to +`value`, and the second time the tag fires, `key` will return +`otherValue`. This is understandable, and it’s how Google Tag Manager +has always worked. + +However, if the template uses `copyFromDataLayer` in an asynchronous +method, or if the tag is part of a sequence, then when the first tag +fires, it’s possible that `key` will actually return `otherValue`, +because by the time the tag resolves that code, the second push will +have happened and the values stored in GTM’s data model will have been +updated. + +This is something to be mindful of. I hope that we get the chance to +control this behavior by providing a flag that lets us choose whether to +use this asynchronous behavior or to fall back to the original, +synchronous process. + +##### The “global variable” APIs + +Templates offer you a handful of APIs that all interact with the global +namespace (namely, the `window` object). These APIs are: + +- [`aliasInWindow`](https://developers.google.com/tag-manager/templates/api#aliasinwindow) + for creating a copy of a global variable in another global variable. + +- [`callInWindow`](https://developers.google.com/tag-manager/templates/api#callinwindow) + for executing a global function. + +- [`copyFromWindow`](https://developers.google.com/tag-manager/templates/api#copyfromwindow) + for creating a local copy (a **proper copy**, NOT a **reference** to + the original) of the global variable. + +- [`createArgumentsQueue`](https://developers.google.com/tag-manager/templates/api#createargumentsqueue) + for creating an array as well as a helper function that passes its + arguments to the array. + +- [`createQueue`](https://developers.google.com/tag-manager/templates/api#createqueue) + for creating an array as a global variable. + +- [`setInWindow`](https://developers.google.com/tag-manager/templates/api#setinwindow) + for setting a global variable. + +These APIs have a number of permissions associated with them, so you +need to make sure you make [the necessary +modifications](#accesses-global-variables) to the template permissions. + +Another thing to keep in mind is that when you access these global +variables in the context of the templates, GTM creates a **local copy** +of each and does NOT copy objects by reference, which is the typical way +of handling JavaScript objects. + +See this example: + +``` +const copy = require('copyFromWindow') +const obj = copy('someObject'); + +obj.someProperty = true; +``` + +This sets `someProperty` on the object to `true` **only in the template +code**. It doesn’t change it to `true` in the global object itself. +That’s because GTM creates a clone of the global variable rather than a +reference to it. + +Finally, GTM handles **functions** in a special way. When you try to run +`setInWindow('someVariable', someFunction)`, where `someFunction` is a +function you have created, what gets set in the global variable +`someVariable` is **not** the actual function, but rather a wrapper +created by GTM which ends up calling the function. + +This shouldn’t be a big deal - since the end result is always the same. +Whatever you call the global variable with gets executed in the function +you created. + +However, it does mean that you won’t be able to **set individual +properties to that function**. Take this example: + +``` +const func = str => str + " Simo"; +func.loaded = true; + +setInWindow('someFunction', func); +``` + +If you now call `window.someFunction('Hello');`, the code ends up +returning `"Hello Simo"`, so it works. However, if you check +`someFunction.loaded`, you’ll notice it’s undefined when it should be +`true`. + +##### The `makeTableMap` API + +The +[`makeTableMap`](https://developers.google.com/tag-manager/templates/api#maketablemap) +API makes the [simple table](#simple-table) field more manageable. + +The simple table field itself returns an array of objects, where each +object represents a **row** of the table and is comprised of key-value +pairs. Each key-value pair corresponds to a column. + +For example, if the simple table had two columns: + +**Column 1:** fieldToSetName +**Column 2:** fieldToSetValue + +The resulting `data` object would look like this: + +``` +[ + { + "fieldToSetName": "page", + "fieldToSetValue": "/home/" + }, + { + "fieldToSetName": "userId", + "fieldToSetValue": "abc123" + } +] +``` + +The `makeTableMap` API turns this into a single object whose contents +are mapped from the values the user input. Naturally, this means that +the column you use as the “key” of this new map must have unique values. + +To continue the example from above, if you run `makeTableMap` against +the array above, this is the result: + +``` +const makeTableMap = require('makeTableMap'); +const log = require('logToConsole'); + +const data = data.properties; // This is the array from the example above +const newMap = makeTableMap(data, 'fieldToSetName', 'fieldToSetValue'); + +log(newMap); +/* LOGS: + { + "page": "/home", + "userId": "abc123" + } +*/ +``` + +#### The `data` object + +If you want to access values the user has input into the template +fields, you need to use the `data` object. Furthermore, to signal +**tag** template completion (or failure), you will also need to use the +`data` object in your template code. + +Every single user input in the template fields is encoded in the `data` +object as properties, where the property name matches the **field name** +you gave in the editor. + +[](https://www.simoahava.com/images/2019/02/field-name.jpg "Field name") + +To access the values the user input into the `cookieSettings` field +above, you would use this syntax: + +``` +const input = data.cookieSettings; +``` + +You don’t need to `require()` any API to access the user input - the +`data` object is always available for fetching the values the user has +entered. Read through the [Fields editor](#fields-editor) chapter to see +how each different field type is encoded into the `data`object. + +#### Variable templates in the code editor + +Variable templates have just one requirement, similar to Custom +JavaScript variables in GTM: they must **return** a value. If you don’t +have a `return` statement in the template, the variable will always +return `undefined` (not very useful). + +``` +const userInput = data.someNumber; + +return userInput * 2; +``` + +The variable template above would take the value entered by user into +the `someNumber` field and return it multiplied by 2. + +#### Tag templates in the code editor + +With tag templates, you **must** invoke one of two methods in the code: + +- `data.gtmOnSuccess()` to indicate that the tag was a success. + +- `data.gtmOnFailure()` to indicate that the tag execution failed. + +I recommend to always have `data.gtmOnSuccess()` (after all, why create +a tag that doesn’t indicate successful completion). If there is a clear +point of failure, such as something you want to block a [tag +sequence](https://www.simoahava.com/analytics/understanding-tag-sequencing-in-google-tag-manager/) +with, you should also add a `data.gtmOnFailure()` call into the code. + +Try to avoid paths in the code that do not lead to either +`data.gtmOnSuccess()` or `data.gtmOnFailure()`, as the tag will be in +“Still running” status for perpetuity. + +Here’s an example. Let’s say the template’s purpose is to write a cookie +into the browser storage. If the template permissions allow the cookie +to be written, `data.gtmOnSuccess()` is called after the write. If the +permissions prevent this, a warning is logged into the console and +`data.gtmOnFailure()` is run instead. + +``` +const log = require('logToConsole'); +const setCookie = require('setCookie'); +const queryPermission = require('queryPermission'); + +if (queryPermission('set_cookies', '_gaClientId')) { + setCookie('_gaClientId', 'abc123'); + data.gtmOnSuccess(); +} else { + log('Unable to write cookie due to missing permissions!'); + data.gtmOnFailure(); +} +``` + +Tag success/failure/incompletion status is shown in Preview mode, too. + +[](https://www.simoahava.com/images/2019/02/firing-status.jpg "Firing status in debug mode") + +#### Client templates in the code editor + +With **Client** templates, you need to invoke the [`claimRequest` +API](https://developers.google.com/tag-manager/serverside/api#claimrequest) +when you want the Client to parse the HTTP request and not let other +Clients use it any longer. For example, if you have a Client that is +designed to handle requests coming to `/my-pixel/`, you’d build a Client +like this: + +``` +const getRequestPath = require('getRequestPath'); +const claimRequest = require('claimRequest'); + +if (getRequestPath() === '/my-pixel/') { + claimRequest(); +} +``` + +When the Client has done its work, it must return a response back to the +source of the request. This is done with the [`returnResponse` +API](https://developers.google.com/tag-manager/serverside/api#returnresponse). + +``` +runContainer(event, () => { + // onComplete callback called, return the response. + returnResponse(); +}); +``` + +I do recommend to read [this +article](https://www.simoahava.com/gtm-tips/build-custom-universal-analytics-client-server-side-tagging/) +for an overview of how Client templates work. + +### Permissions + +When you add one of the **supported APIs** using the `require` method in +the code editor, the associated permissions for that API are +automatically displayed in the **Permissions** tab. + +[](https://www.simoahava.com/images/2019/03/tag-template-permissions-tab.jpg "Permissions tab") + +Read [this +document](https://developers.google.com/tag-manager/serverside/permissions) +for a comprehensive list of permissions used by **Server-side tagging** +templates. + +Permissions are described at length [in the official +documentation](https://developers.google.com/tag-manager/templates/permissions). +Nevertheless, in this chapter I’ll show what the UI for each permission +looks like, and what the different settings are used for. + +Note that you can **save** a template with code that conflicts with a +permission. It’s not until the tag is run that an error is thrown, and +this error is surfaced in the [Errors +tab](https://www.simoahava.com/analytics/new-errors-tab-preview-mode/) +in Preview mode, signalling that there was a permissions conflict within +the tag. + +#### Accesses Global Variables + +[](https://www.simoahava.com/images/2019/03/permissions-access-global-variables.jpg "Accesses global variables") + +The Accesses Global Variables permission allows the code to **Read** +(see what value is assigned to the variable), **Write** (update the +value assigned to the variable), and **Execute** (if the variable is a +function, execute it) global variables. The **Key** is the name of the +global variable, accessed via `window[key]`. + +Here are the APIs and the relevant permissions for them: + +| API example | Permission | +|--------------------------------------------------|------------------------------------------------------------------| +| `aliasInWindow('copyTo', 'copyFrom')` | **Write** on `copyTo`, **Read** on `copyFrom`. | +| `callInWindow('someFunction')` | **Execute** on `someFunction`. | +| `copyFromWindow('copyFrom')` | **Read** on `copyFrom`. | +| `createArgumentsQueue('helper', 'queue')` | **ReadWrite** on `helper`, **ReadWrite** on `queue`. | +| `createQueue('someArray')` | **ReadWrite** on `someArray`. | +| `setInWindow('someVariable', 'someValue', true)` | **ReadWrite** on `someVariable` (regardless of third parameter). | + +In other words, if your code needs to access any global variable using +e.g. `setInWindow` or `copyFromWindow`, you need to add those variables +into these permission settings, and you need to specify if the code can +read, write, and/or execute the variable in question. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`access_globals`](#access_globals). + +#### Accesses Local Storage + +[](https://www.simoahava.com/images/2020/08/permissions-access-local-storage.jpg "Accesses Local Storage") + +The Accesses Local Storage permission allows the code to **Read** (get +items from `localStorage`) and **Write** (set items in `localStorage`) +items in the browser’s `localStorage`, by utilizing the `localStorage` +template API. + +If your code needs to access `localStorage`, you need to specify Read +and/or Write permissions for every key in `localStorage` the template +needs to access. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`access_local_storage`](#access_local_storage). + +#### Accesses Template Storage + +[](https://www.simoahava.com/images/2020/08/permissions-accesses-template-storage.jpg "Accesses Template Storage") + +The Accesses Template Storage permission allows the code to Read and +Write to a temporary storage which exists for the current page load. +It’s useful if you need to persist information that persists across +template executions. + +For example, if you want to make sure your template doesn’t run some +code more than once per page load, you could use `templateStorage` like +this: + +``` +const templateStorage = require('templateStorage'); +const ids = templateStorage.getItem('ids') || []; +if (!ids.includes(data.id)) { + // Run code + ... + ids.push(data.id); + templateStorage.setItem('ids', ids); +} +``` + +The permission has no configuration - read, write, and delete operations +are always permitted. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`access_template_storage`](#access_template_storage). + +#### Reads Cookie Value(s) + +[](https://www.simoahava.com/images/2019/03/permissions-reads-cookie-values.jpg "Reads cookie values") + +The Reads Cookie Value(s) permission lists all the first-party cookies +the tag or variable code can access with the `getCookieValues` API. To +allow the code to read from a first-party cookie, you need to list all +the cookie names that can be accessed on **their own line** in the text +box in the permission settings. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`get_cookies`](#get_cookies). + +#### Reads Referrer URL + +[](https://www.simoahava.com/images/2019/05/permissions-gets-referrer.jpg "Gets referrer") + +The Reads Referrer URL permission allows template code to access any or +only parts of the referring page URL (from `document.referrer`. You can +restrict access to any combination of its URL components. Since the +permission model is the same as for the [Reads URL](#reads-url) +permission, please read the very next section for more details. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`get_referrer`](#get_referrer). + +#### Reads URL + +[](https://www.simoahava.com/images/2019/03/permissions-reads-url.jpg "Reads URL") + +The Reads URL permission allows template code to access any or only +parts of the current page URL. You can restrict access to any +combination of the following URL components: + +| Component | Example | +|-----------|----------------------------| +| protocol | `http`, `https`, or `file` | +| host | `blog.simoahava.com` | +| port | `443` | +| path | `/analytics/articles/` | +| extension | `html` | +| fragment | `about-us` | +| query | `gclid=1.2.3.4` | + +After selecting **query**, You can specify which **query keys** the +`getUrl` API is allowed to access, or you can leave it to its default +setting which is any query keys. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`get_url`](#get-url). + +#### Injects Hidden Iframes + +[](https://www.simoahava.com/images/2019/03/permissions-injects-hidden-iframes.jpg "Injects Hidden Iframes") + +You can list a number of URL patterns, each on its own row, that must +match those in the code editor used to inject hidden iframes on the +page. The `src` attribute of the iframe the editor wants to inject must +match one of these parameters. + +The URL patterns must include `https://`, a valid hostname, and a valid +path. Hostnames can use asterisk to wildcard match any subdomains, and +paths can use asterisk to wildcard match any characters. Path ending +with a `/` is also a wildcard match for anything that follows. + +For example, given these three patterns: + +- `https://*.simoahava.com/tracker.html` + +- `https://www.gtmtools.com/` + +- `https://www.tracksimo.com/*tracker.html` + +Any of these will be **valid** matches: + +- `https://simoahava.com/tracker.html` + +- `https://blog.tracker.simoahava.com/tracker.html` + +- `https://www.gtmtools.com/track/` + +- `https://www.gtmtools.com/tracking/this/` + +- `https://www.tracksimo.com/tracker.html` + +- `https://www.tracksimo.com/track/superdupertracker.html` + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`inject_hidden_iframe`](#inject_hidden_iframe). + +#### Injects Scripts + +[](https://www.simoahava.com/images/2019/03/permissions-injects-scripts.jpg "Injects Scripts") + +You can list a number of URL patterns, each on its own row, that must +match those in the code editor used to inject scripts on the page. The +`src` attribute of the script the editor wants to inject must match one +of these patterns. + +The URL patterns must include `https://`, a valid hostname, and a valid +path. Hostnames can use asterisk to wildcard match any subdomains, and +paths can use asterisk to wildcard match any characters. Path that only +consists of a `/` is also a wildcard match for anything that follows. + +For example, given these three patterns: + +- `https://*.simoahava.com/tracker.js` + +- `https://www.gtmtools.com/` + +- `https://www.tracksimo.com/*tracker.js` + +Any of these will be **valid** matches: + +- `https://simoahava.com/tracker.js` + +- `https://blog.tracker.simoahava.com/tracker.js` + +- `https://www.gtmtools.com/tracking/gtmtracker.js` + +- `https://www.tracksimo.com/tracker.js` + +- `https://www.tracksimo.com/track/superdupertracker.js` + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`inject_script`](#inject_script). + +#### Logs To Console + +[](https://www.simoahava.com/images/2019/03/permissions-logs-to-console.jpg "Logs to Console") + +You can choose whether the `logToConsole` API can log into the browser +console only when in preview/debug mode, or whether it can log to +console whenever the tag fires, regardless of debug context. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`logging`](#logging). + +#### Reads Data Layer + +[](https://www.simoahava.com/images/2019/03/permissions-reads-data-layer.jpg "Reads Data Layer") + +In the permission configuration, add the Data Layer keys the code has +access to, each on its own row. + +You can use wildcards to allow the code access to any subproperties of +the key. For example, a permission like this: + +- `ecommerce.*` + +Will allow the code editor to read `ecommerce`, +`ecommerce.purchase.actionField.id`, `ecommerce.purchase.products`, and +any other key nested under `ecommerce`. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`read_data_layer`](#read_data_layer). + +#### Reads Document Character Set + +[](https://www.simoahava.com/images/2019/03/permissions-reads-document-character-set.jpg "Reads Document Character Set") + +An extremely simple permission for an extremely simple API. This +permission governs whether or not the code editor can use the +`readCharacterSet` API, which, in turn, returns the value of +`document.characterSet`. + +There are no configuration options you can pass to the permission, so +it’s always permitted (unless a policy is used to block it). + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`read_character_set`](#read_character_set). + +#### Reads Container Data + +[](https://www.simoahava.com/images/2020/08/permissions-reads-container-data.jpg "Reads container data") + +Very simple permission (nothing to configure), which is used when the +template code needs to use the `getContainerVersion` API. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`read_container_data`](#read_container_data). + +#### Reads Event Metadata + +[](https://www.simoahava.com/images/2019/07/reads-event-metadata.jpg "Reads Event Metadata") + +This permission allows the code to use the `addEventCallback` API, which +updates the `eventCallback` of the `dataLayer.push()` that triggered the +tag created from this template. A data object of tags that fired for the +`dataLayer` event is passed as an argument to the callback. + +Take a look at [this +article](https://www.simoahava.com/analytics/google-tag-manager-monitor/) +for more details on how this API works. + +This permission doesn’t take any configuration options, so it’s always +permitted (unless a policy is used to block it). + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): +> [`read_event_metadata`](#read_event_metadata). + +#### Reads Document Title + +[](https://www.simoahava.com/images/2019/03/permissions-reads-document-title.jpg "Reads Document Title") + +Another really simple permission for a really simple API. This +permission allows the code to use the `readTitle` API, which returns the +value of `document.title`. + +This permission doesn’t take any configuration options, so it’s always +permitted (unless a policy is used to block it). + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`read_title`](#read_title). + +#### Sends Pixels + +[](https://www.simoahava.com/images/2019/03/permissions-sends-pixels.jpg "Sends Pixels") + +You can provide a list of URL patterns (each on its own row), and when +using the the `sendPIxel` API, the URL the pixel is dispatched to must +match one of these patterns. + +For example, given these three patterns: + +- `https://*.simoahava.com/collect` + +- `https://www.gtmtools.com/` + +- `https://www.tracksimo.com/*/track` + +Any of these will be **valid** matches: + +- `https://simoahava.com/collect` + +- `https://blog.tracker.simoahava.com/collect` + +- `https://www.gtmtools.com/tracking/collect` + +- `https://www.tracksimo.com/tracker/track` + +- `https://www.tracksimo.com/collect/analytics/track` + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`send_pixel`](#send_pixel). + +#### Sets A Cookie Value + +[](https://www.simoahava.com/images/2019/03/permissions-sets-a-cookie-value.jpg "Sets a Cookie Value") + +This permission lets you configure which cookies the template code is +allowed to set. You can also configure the following parameters per +cookie: + +- **Domain** - on which domain the cookie can be written on or `*` for + any. + +- **Path** - on which path the cookie can be written on or `*` for any. + +- **Secure** - whether the cookie must be set with the `secure` flag, + without the `secure` flag, or either. + +- **Session** - whether the cookie must be a session cookie, a cookie + with an expiration, or either. + +> Name used with the [`queryPermission`](#the-querypermission-api) API +> and [policies](#policies-reference): [`set_cookies`](#set_cookies). + +### Tests + +For more information on writing and running **tests** against your +template code, see [this chapter](#tests), and read [this +article](https://www.simoahava.com/analytics/writing-tests-for-custom-templates-google-tag-manager/). + +### Template preview + +The **Template Preview** window is where you can see what your template +user interface looks like in its current state, and you can also try +filling in the inputs before [testing](#testing-the-template) the +template. + +[](https://www.simoahava.com/images/2019/04/template-preview-editor.jpg "Template preview editor") + +When you make changes to the template, whether in the **Info**, +**Fields**, **Code**, or **Permission** tabs, the **Refresh** icon +appears. By clicking this icon, the template preview is updated to +reflect the changes you have made to the template in the editor. + +Other than that, the template preview should perform exactly as the real +thing. In the **summary view**, i.e. before you click it for editing, +the template will show you only those fields that are included in +summary. You can manually toggle this with the [Always in +summary](#always-in-summary) field configuration. + +The template preview is the perfect place to try out your template user +interface before testing how the template actually runs, and finally +saving it into the template library of your container. + +### Importing and exporting + +**Importing and exporting** templates will almost certainly be one of +the most useful things you can do with templates. By exporting your +custom templates, you can create a store or library of Google Tag +Manager templates for others to use. By importing templates, you can add +custom templates created by others into your own template library. + +**To export a template**, click open the editor menu and choose +**Export**. + +[](https://www.simoahava.com/images/2019/04/export-import-template.jpg "Export and import template") + +Your browser should automatically download a file named +`.tpl`. The **TPL** suffix is a custom file format +for Google Tag Manager templates. If you open the file in a text editor, +you can see that it’s a combination of JSON objects and plain text. + +[](https://www.simoahava.com/images/2019/04/template-export-file.jpg "Template export file") + +**To import a template**, click open the editor menu and choose +**Import**. When importing a template, it’s a good idea to first +**create a new template** which will host the imported item. Unless of +course you are deliberately updating an existing template with a newer +version of the import. + +Once you’ve chosen the file, a pop-up will warn you that importing the +file will overwrite the template currently being edited. + +Why is this significant? Well, an **import completely overwrites the +template to which it is imported**. So if you expect some sort of +“merging” being an option, well, at the time of writing there’s no such +feature. + +### Advanced settings + +When you select **Show Advanced Settings** from the editor menu, a +number of changes takes place in the UI. + +[](https://www.simoahava.com/images/2019/04/show-advanced-settings-editor.jpg "Show advanced settings") + +1. A **Notes** option becomes visible in the editor navigation. You can + write anything you want into this text area, such as developer + documentation, instructions for use, etc. The **Notes** field + contents are included with the template export/import. + +[](https://www.simoahava.com/images/2019/04/editor-notes.jpg "Editor Notes") + +2. The **Info** tab will now show the **Version** (of the templating + system itself) and the **Container Context** (whether the template + is for web containers or for app containers). You can’t change + either of these values - they are set when you start creating the + template. The **Info** tab will also show the **Brand Name** field. + +[](https://www.simoahava.com/images/2019/04/version-context-info.jpg "Version and context info") + +3. In the **Info** and **Fields** tabs, you can directly edit the JSON + source. Note that even though this lets you edit anything you want + in the source, trying to change things that can’t be changed (such + as **Version** and **Container Context** from above) will result in + the template issuing a warning when you try to refresh it in the + preview or save it. + +[](https://www.simoahava.com/images/2019/04/source-toggle.jpg "Source toggler") + +### Running the template code + +Whenever you want to test how the template actually **runs**, you can +click the **Run code** button in the Template Preview window. This +executes the tag/variable template code itself, outputting any debug +messages into the console. + +[](https://www.simoahava.com/images/2019/04/test-example.jpg "Test example") + +For **tag templates**, the console will tell you when the preview was +last refreshed. It will also output any test logs, such as test start +and completion (with elapsed time), as well as any errors. + +If you don’t change anything in the [code editor](#code-editor), the +default code snippet will output the contents of the `data` object into +the console. + +Generally, the console will display any strings you write into the +console using the +[`logToConsole`](https://developers.google.com/tag-manager/templates/api#logtoconsole) +API. + +[](https://www.simoahava.com/images/2019/04/error-in-console.jpg "Error in console") + +Furthermore, when you choose to **Show test page** in the editor menu, a +content area will pop up which you can **inspect** to find any changes +the tag code has made to the page on which the tag runs. This is useful +if you want to check if and how your hidden iframe was added to the +page, for example. + +[](https://www.simoahava.com/images/2019/04/show-test-page.jpg "Show test page") + +To find your modifications to the test page, right-click it in your +browser and choose **Inspect** (this may vary by browser). Start +drilling down the DOM until you find an iframe with GTM’s sandbox HTML +file. The `` of that iframe should contain any modifications your +tag did to the page. + +[](https://www.simoahava.com/images/2019/04/test-page-inspect.jpg "Inspect test page") + +**Variable templates** differ in that they don’t modify the underlying +page (or, they SHOULDN’T modify the underlying page). Instead, when you +**test** the template, the console outputs what the variable will +return. + +[](https://www.simoahava.com/images/2019/04/test-template-variable.jpg "Test variable template") + +## Templates in GTM’s Preview mode + +In **Preview** mode, tag and variable templates work just like their +built-in template counterparts. + +For **tags**, you’ll see the tag **name** and **properties**. + +Two default properties are always included: + +1. **Type** - which is the template name, basically. + +2. **Firing Status** - which will show “Succeeded” if + `data.gtmOnSuccess()` was reached in the tag code, “Failure” if + `data.gtmOnFailure()` was reached in the tag code, and + `Still running` if neither was reached or if the tag is, actually, + still running (due to e.g. the endpoint timing out). + +The rest of the properties mirror the fields and [field +configurations](#field-configuration-reference) you have generated. In +the example below, a [text input](#text-input) field named **Iframe +URL** has been populated with the value `https://www.gtmtools.com/`, and +a [param table](#param-table) field named **Iframe parameters** has been +populated with the values you see. + +[](https://www.simoahava.com/images/2019/04/tag-template-parameters.jpg "Tag template parameters") + +For **variables**, the output can be found in the **Variables** tab of +Preview mode, and you can see the return value of each custom variable +in exactly the same way you can see the return values for all predefined +variable templates. + +The variable template **type** will be the name of the custom template +(“Multiply” in the example below). + +[](https://www.simoahava.com/images/2019/04/variable-template-example.jpg "Variable template example") + +The **Errors** tab will surface any errors thrown by +[policies](#policies-reference) or if the template failed a +[permission](#permissions) check. + +[](https://www.simoahava.com/images/2019/01/tag-errors.jpg "Tag errors") + +## Field configuration reference + +This chapter lists all the **Field configuration** options you can +configure for individual fields. Remember to check out the [Fields +editor](#fields-editor) chapter for a detailed description of the fields +and the configurations available to them. + +[](https://www.simoahava.com/images/2019/02/field-configuration.jpg "Field configuration") + +### “Edit row” dialog title + +[](https://www.simoahava.com/images/2019/02/reference-edit-row.jpg "Edit row dialog title") + +**Description**: The **“Edit row” dialog title** is available in fields +where the user adds rows to a table and can edit those rows in an +overlay. Defaults to “Edit row”. + +**How it works**: The text you write into the configuration field will +appear as the title of the overlay which pops out when the user +**edits** a row they have already added to the table. + +**Used in**: [Param table](#param-table). + +### “New row” button text + +[](https://www.simoahava.com/images/2019/02/reference-new-row.jpg "New row button text") + +**Description**: The **“New row” button text** determines the button +text with which the user can add a new row to a table field. + +**How it works**: The text you type into the configuration field will be +the text of the button below the table, which the user can use to add +new rows to the table. Defaults to “Add row”. + +**Used in**: [Param table](#param-table), [Simple table](#simple-table). + +### “New row” dialog title + +[](https://www.simoahava.com/images/2019/02/reference-new-row-dialog.jpg "New row dialog title") + +**Description**: The **“New row” dialog title** determines the text you +see as the heading of the overlay that pops out when you choose to add a +new row into a table. Defaults to “New row”. + +**How it works**: Type the new title into the configuration field, and +it will show as the heading of the overlay the user sees when adding a +new row to a table that uses overlays for data input. + +**Used in**: [Param table](#param-table). + +### “Not set” option + +[](https://www.simoahava.com/images/2019/02/reference-not-set-option.jpg "not set option") + +**Description**: Before the user chooses an item in the drop-down list, +you can use the **“Not set” option** to show a placeholder value. + +**How it works**: The **“Not set” option** is a selectable option in the +drop-down list, which also shows up if the user hasn’t made any +selection yet. If the user leaves the “Not set” option as the selected +item, the value of the field in the code editor will be a **blank +string**. + +**Used in**: [Drop-down menu](#drop-down-menu). + +### Allow empty strings + +**Description**: The **Allow empty strings** checkbox lets you determine +whether or not an empty text input field shows up as an empty string +when accessing the field value in the `data` object. + +**How it works**: If the checkbox is checked, the text input field value +in the code editor will be an empty string. If unchecked or if the +configuration hasn’t been added to the field, the value will be +`undefined` for empty text fields. + +**Used in**: [Text input](#text-input) + +### Always in summary + +[](https://www.simoahava.com/images/2019/02/reference-always-in-summary.jpg "Always in summary") + +**Description**: The **Always in summary** configuration determines +whether the field name and current value will show up in the summary +view of the item. + +**How it works**: When the checkbox is toggled, the user will see the +name (if [Display name](#display-name) is configured) and current value +of the field in the summary view if the item. The summary is view what +you see when the tag or variable is not in edit mode. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Simple +table](#simple-table), [Param table](#param-table). + +### Clear on copy + +**Description**: With **Clear on copy**, you can toggle whether or not +the field will retain or clear its value if a copy is made of the tag or +variable. + +**How it works**: If **Clear on copy** is checked, then when the user +makes a copy of an item created with this template, the field will have +its value cleared (returned to its initial state) in the copy. If +**Clear on copy** is unchecked or missing, the value of the field from +the original item will be preserved in the copy. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table). + +### Default value + +[](https://www.simoahava.com/images/2019/02/reference-default-value.jpg "Default value") + +**Description**: The **Default value** option determines what the value +of the field is before the user inputs anything into the field. + +**How it works**: **Default value** represents the *initial* value of +the field. It is considered a true value, meaning if the user doesn’t +delete or change it, the default value will be what the field returns in +the code editor. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table). + +### Display line count + +[](https://www.simoahava.com/images/2019/02/reference-line-count.jpg "Line count") + +**Description**: Set the height of the text input area, and whether or +not a multi-line value can be given. + +**How it works**: If **Display line count** is set to `1`, then only one +row of data can be input into the field. Anything larger than `1`, and +the height of the text area grows as a result, and the user can type on +multiple rows in the text input field. + +**Used in**: [Text input](#text-input). + +### Display message when not set + +[](https://www.simoahava.com/images/2019/02/reference-display-message-not-set.jpg "Display message when not set") + +**Description**: The **Display message when not set** allows you to show +a “default value” for the field when in the summary view and the field +doesn’t have a value. + +**How it works**: The text will show up only in the summary view when +the field is not set, i.e. has no determinable value. It will not impact +what the code editor returns as the field value. + +**Used in**: [Text input](#text-input), [Simple table](#simple-table), +[Param table](#param-table). + +### Display name + +[](https://www.simoahava.com/images/2019/02/reference-display-name.jpg "Display name") + +**Description**: The **Display name** configuration lets you set a label +for the field. + +**How it works**: The text you input in the **Display name** is shown as +a label for the field when both in edit mode and the summary view. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table), [Group](#group), [Label](#label). + +### Enabling conditions + +[](https://www.simoahava.com/images/2019/02/reference-enabling-conditions.jpg "Enabling conditions") + +**Description**: You can establish a dependency with **Enabling +conditions**. The field will only be visible in the UI if the enabling +condition validates. + +**How it works**: An *Enabling condition* is essentially a check against +**some other field’s value**. You can use it to check whether some +checkbox is unchecked, for example (as in the screenshot above). + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table), [Group](#group), [Label](#label). + +### Group style + +[](https://www.simoahava.com/images/2019/02/reference-group-style.jpg "Group style") + +**Description**: Use the **Group style** configuration to determine how +the [Group](#group) field works. + +**How it works**: The options you can choose are: + +1. Simple section: the fields are simply grouped without UI impact. + This is useful if you simply want to have the field configurations + for the group impact the nested fields within. + +2. Collapsible section – Open: The fields are in a section that can be + collapsed, and the section defaults to being open. + +3. Collapsible section – Closed: The fields are in a section that can + be collapsed, and the section defaults to being closed. + +4. Collapsible section – Open if not default: The fields are in a + section that can be collapsed, and if the fields have not been + edited, the section is closed. + +**Used in**: [Group](#group). + +### Help text + +[](https://www.simoahava.com/images/2019/02/reference-help-text.jpg "Help text") + +**Description**: Use **Help text** to show a little tooltip when +hovering over the question mark icon next to the field in the UI. + +**How it works**: The text you type into the **Help text** configuration +field will appear in the UI when the user hovers the mouse cursor over +the little question mark icon. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table), [Group](#group). + +### Include variables + +[](https://www.simoahava.com/images/2019/02/reference-include-variables.jpg "Include variables") + +**Description**: When checked, **Include variables** will make the full +list of GTM variables available for selection as the field value. + +**How it works**: The drop-down menu will include all the GTM variables +as selectable options if this field configuration is checked. + +**Used in**: [Drop-down menu](#drop-down-menu). + +### Nested fields + +[](https://www.simoahava.com/images/2019/02/reference-nested-fields.jpg "Nested fields") + +**Description**: When you add **Nested fields** to a field, those fields +become dependent on the enabling condition set for the parent field. +Also, visually they will be placed closer to the parent field compared +to if they were regular, non-nested fields. + +**How it works**: Toggle **Nested fields** on, and you can add any +available field type as a nested field of the current field. After that, +if the parent field is disabled due to an invalid [Enabling +condition](#enabling-conditions), for example, the nested fields will be +disabled, too. + +**Used in**: [Drop-down menu](#drop-down-menu), [Checkbox](#checkbox), +[Radio buttons](#radio-buttons). + +### Text as list + +[](https://www.simoahava.com/images/2019/02/reference-text-as-list.jpg "Text as list") + +**Description**: Use this with [Line count](#line-count) to access the +field value as an array of strings (where each row corresponds to an +item in the array). + +**How it works**: When **Text as list** is checked, then each row in a +[Text input](#text-input) field (when Line count is also configured) +will be an item in the array of the resulting `data` object. Without +**Text as list**, a multi-line input will result in a single string, +where each row is separated with the newline character (`\n`). + +**Used in**: [Text input](#text-input). + +### Validation rules + +[](https://www.simoahava.com/images/2019/02/reference-validation-rules.jpg "Validation rules") + +**Description**: Use the **Validation rules** configuration to establish +validation criteria for the field. + +**How it works**: When you add a **Validation rule** to the field, the +field must pass the validation, or the user won’t be able to save the +tag or variable. The available rules are: + +| Rule | How to fail validation | +|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| …cannot be empty | User does not input anything into the field. | +| …must be a string of the required length | User inputs a string that is less than the minimum or more than the maximum length. | +| …must be a number | User inputs a value that is not an **integer** number. | +| …must be a positive number | User inputs an **integer** number that is equal to or less than 0. | +| …must be a positive number or 0 | User inputs an **integer** number that is less than 0. | +| …must be an integer between 0 and 100 (inclusive) | User inputs an **integer** number that is less than 0 or more than 100. | +| …must be a number between 0 and 1 (inclusive) | User inputs a number (integer or floating point) which is less than 0.0 or more than 1.0. | +| …must match a regular expression | User inputs a value that does not match the given **regular expression**. The RegEx accepts **full matches only**, so don’t forget to add leading and/or trailing `.*` if you want it to be an open-ended match. | +| …must be a valid GA tracking ID | User inputs a value that does not match the GA tracking ID format (UA-11111-1). | +| …must be a list of the required length | User inputs rows into a table fewer than the minimum length or more than the maximum length. Applies also to a Text input field where [Text as list](#text-as-list) is being used. | + +> **NOTE!** For fields that are governed by [enabling +> conditions](#enabling-conditions), the validation rule will only apply +> if the field has been enabled. + +You can click the **action menu** for the Validation rule configuration +to **Show advanced settings**. + +[](https://www.simoahava.com/images/2019/02/validation-show-advanced-settings.jpg "Show advanced settings") + +The advanced settings include: + +- **Error messages**: You can customize the error message that is shown + if the field does not pass validation. + +- **Enabling conditions**: You can add [enabling + conditions](#enabling-conditions) to the **validation rule itself**, + meaning **if the enabling conditions do not pass**, the **validation + rule is ignored**. + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu), [Checkbox](#checkbox), [Radio +buttons](#radio-buttons), [Simple table](#simple-table), [Param +table](#param-table). + +### Value hint + +[](https://www.simoahava.com/images/2019/03/reference-value-hint.jpg "Value hint") + +**Description**: The text you enter here will be shown as a +*placeholder* value in the field before the user has added any input. + +**How it works**: The text will simply be a placeholder - it will not be +the value of the field if saved without any input. + +**Used in**: [Text input](#text-input). + +### Value unit + +[](https://www.simoahava.com/images/2019/03/reference-value-unit.jpg "Value unit") + +**Description**: Use this to guide the users on what type of value is +expected in the field. + +**How it works**: The text will be displayed to the right of the field, +giving users indication what type of value is expected in the field. The +text can be anything (i.e. isn’t restricted to e.g. JavaScript types). + +**Used in**: [Text input](#text-input), [Drop-down +menu](#drop-down-menu). + +## Policies reference + +**Policies** are directives the site admin adds to the page template +(or, if necessarily, dynamically with JavaScript), which dictate the +type of API permissions each custom template can use. + +They differ from [Permissions](#permissions) in one critical detail: +where permissions are built into the template, and specify a broader set +of API configurations the template can run with, policies are +implemented by the site where these templates are run. + +In other words, template permissions come from the vendor, template +policies come from the site owner. Both have the capability to delimit +or restrict the types of templates that can run on any given page. + +To provide a policy, you need to use some [`gtag.js` +code](https://developers.google.com/analytics/devguides/collection/gtagjs/) +(note, you do **not** have to install the `gtag.js` snippet!) where you +specify the permission requests your site will listen to. + +For example, if you have a template that is trying to send a pixel to an +endpoint whitelisted in the template’s permissions, you can use a policy +to make sure that the template can actually only send the request to one +specific endpoint. See the example below. + +``` +window.dataLayer = window.dataLayer || []; +function gtag(){dataLayer.push(arguments);} + +gtag('policy', 'send_pixel', function(container, policy, data) { + // Only restrict the policy to one specific container by returning true + // for all other containers + if (container !== 'GTM-ABCDE') { + return true; + } + + // Check if the URL of the pixel request is a specific endpoint, + // and if it isn't throw an error and prevent the tag from working. + if (data.url !== 'https://snowplow.simoahava.com/i') { + throw('Invalid pixel endpoint!'); + } else { + return true; + } +}); +``` + +Note: The policy **must** return `true` explicitly if you want the check +to pass. The policy will always default to returning `false` and +preventing the template from working. + +You can add more than one policy to the page, each in its own `gtag()` +command, or you can check against ALL permissions requests by setting +the policy name to `all` as below: + +``` +window.dataLayer = window.dataLayer || []; +function gtag(){dataLayer.push(arguments);} + +gtag('policy', 'all', function(container, policy, data) { + // Prevent sending pixels and injecting iframes + if (policy === 'send_pixel' || policy === 'inject_hidden_iframe') { + throw 'Sending pixels and injecting hidden iframes blocked!'; + } else { + return true; + } +}); +``` + +Please read the [official policy +documentation](https://developers.google.com/tag-manager/templates/policies) +for more details on how policies work. + +Below are listed all the policy names and what the `data` object +comprises for each. + +### access_globals + +**Triggered by APIs**: + +- [`aliasInWindow`](https://developers.google.com/tag-manager/templates/api#aliasinwindow) + (once for the `toPath` key and once for the `fromPath` key). + +- [`callInWindow`](https://developers.google.com/tag-manager/templates/api#callinwindow) + +- [`copyFromWindow`](https://developers.google.com/tag-manager/templates/api#copyfromwindow) + +- [`createArgumentsQueue`](https://developers.google.com/tag-manager/templates/api#createargumentsqueue) + +- [`createQueue`](https://developers.google.com/tag-manager/templates/api#createqueue) + +- [`setInWindow`](https://developers.google.com/tag-manager/templates/api#setinwindow) + +**`data` object composition**: + +``` +{ + "key": "some_global_var", // name of the key the permission tries to access + "read": true || false, // does the key have read access + "write": true || false, // does the key have write access + "execute": true || false // does the key have execute access +} +``` + +### get_cookies + +**Triggered by APIs**: + +- [`getCookieValues`](https://developers.google.com/tag-manager/templates/api#getcookievalues) + +**`data` object composition**: + +``` +{ + "name": "some_cookie_name" // name of the cookie being accessed +} +``` + +### get_referrer + +**Triggered by APIs**: + +- [`getReferrer`](https://developers.google.com/tag-manager/templates/api#getreferrerurl) + +- [`getReferrerQueryParameters`](https://developers.google.com/tag-manager/templates/api#getreferrerqueryparameters) + +**`data` object composition**: + +``` +{ + "component": "query" // the component of the referrer URL being accessed +} +``` + +### get_url + +**Triggered by APIs**: + +- [`getUrl`](https://developers.google.com/tag-manager/templates/api#geturl) + +**`data` object composition**: + +``` +{ + "component": "protocol" // the URL component name being accessed +} +``` + +### inject_hidden_iframe + +**Triggered by APIs**: + +- [`injectHiddenIframe`](https://developers.google.com/tag-manager/templates/api#injecthiddeniframe) + +**`data` object composition**: + +``` +{ + "url": "https://some-iframe.com/" // the URL of the iframe being injected +} +``` + +### inject_script + +**Triggered by APIs**: + +- [`injectScript`](https://developers.google.com/tag-manager/templates/api#injectscript) + +**`data` object composition**: + +``` +{ + "url": "https://some-script.com/script.js" // the URL of the script being loaded +} +``` + +### logging + +**Triggered by APIs**: + +- [`logToConsole`](https://developers.google.com/tag-manager/templates/api#logtoconsole) + +**There is no `data` object associated with this permission.** + +### read_character_set + +**Triggered by APIs**: + +- [`readCharacterSet`](https://developers.google.com/tag-manager/templates/api#readcharacterset) + +**There is no `data` object associated with this permission.** + +### read_data_layer + +**Triggered by APIs**: + +- [`copyFromDataLayer`](https://developers.google.com/tag-manager/templates/api#copyfromdatalayer) + +**`data` object composition**: + +``` +{ + "key": "some.variable.name" // name of the Data Layer Variable being accessed +} +``` + +### read_event_metadata + +**Triggered by APIs**: + +- [`addEventCallback`](https://developers.google.com/tag-manager/templates/api#addeventcallback) + +**There is no `data` object associated with this permission.** + +### read_title + +**Triggered by APIs**: + +- [`readTitle`](https://developers.google.com/tag-manager/templates/api#readtitle) + +**There is no `data` object associated with this permission.** + +### send_pixel + +**Triggered by APIs**: + +- [`sendPixel`](https://developers.google.com/tag-manager/templates/api#sendpixel) + +**`data` object composition**: + +``` +{ + "url": "https://some-endpoint.com/endpoint" // the URL of the pixel request endpoint +} +``` + +### set_cookies + +**Triggered by APIs**: + +- [`setCookie`](https://developers.google.com/tag-manager/templates/api#sendpixel) + +**`data` object composition**: + +``` +{ + "name": "some_cookie_name", // the name of the cookie + "options": { + "domain": "somedomain.com", // the domain of the cookie + "path": "/some-path/", // the path of the cookie + "max-age": "15000", // the maximum age of the cookie (in seconds) + "expires": "Sun, 11 Aug 2019 10:00:00 GMT", // UTC date string of the cookie's expiration + "secure": true || false, // Secure cookie + "sameSite": true || false // sameSite cookie + } +} +``` + +## Final thoughts + +It’s difficult to compose any sort of summary to what I consider to be +the most extensive and fundamental Google Tag Manager update since the +programmatic API was released almost five years ago. + +Custom templates are, for now, a completely **optional** feature. No one +is forcing you to use them, and you can continue as a happy GTM user +without having to bother about the new **Templates** menu. + +However, I have a hunch that there is a huge incentive for the GTM team +to get rid of the Custom HTML tag and the Custom JavaScript variable. +When talking about governance and the existing prejudices towards GTM, +especially from developers, it often boils down to being able to inject +**any** arbitrary JavaScript code on the site, using the outdated and +questionable `eval()` method, no less. + +With custom templates, many of the problems around governance are +presented with a solution: + +1. The template code is compiled into JavaScript when the container is + created, so `eval()` is no longer used to run the code. + +2. Template-specific permissions (created by the template author) can + delimit what the template does **and** what types of user input are + valid and accepted. + +3. Page-specific policies (created by the site admin) can be used to + further restrict what **any** template can do on the site. + +But these are mostly how templates address **negative** qualities of +GTM - it would be foolish to ignore their net **positive** effect. + +Google Tag Manager is a UI-driven tool. Brands can now use custom +templates to encapsulate their complicated JavaScript activation +mechanisms under a user interface, thus minimizing the possibility of +human error, and demystifying how the vendor JavaScript runs. Thus, +instead of sharing a JavaScript snippet the user has to copy-paste to a +Custom HTML tag, the brand can share a template export that can be added +to the container directly, with all the code in the correct place. + +I can’t wait to see what the community comes up with, too! I’m waiting +for a library of custom templates to emerge, hopefully sanctioned by +Google but moderated by the community (with brands being able to add +**verified** templates, too). + +So now, brave traveller. You have reached the end, though I can only +assume you skipped most of the above. What do you think about custom +templates? Is my hyperbolic exuberance (yet again) unwarranted? + +TAGGED IN +google tag manager +custom templates +guide +tag templates +variable templates + +- NEXT +- PREVIOUS + +- +- +- +- +- +- + + diff --git a/skills/gtm-custom-templates/references/sandboxed-javascript-api.md b/skills/gtm-custom-templates/references/sandboxed-javascript-api.md new file mode 100644 index 0000000..e42d4b0 --- /dev/null +++ b/skills/gtm-custom-templates/references/sandboxed-javascript-api.md @@ -0,0 +1,357 @@ +# Sandboxed JavaScript API Reference + +This is the complete Sandboxed JavaScript API reference for Google Tag Manager custom templates. These APIs enable you to build powerful custom templates while maintaining security and performance standards. + +Source: https://developers.google.com/tag-platform/tag-manager/templates/api + +## API Categories + +### Core APIs + +These APIs work with sandboxed JavaScript to build custom templates in Google Tag Manager. + +#### Consent Management + +- **addConsentListener(consentType, listener)** → void + - Registers a listener function to execute when the state of the specified consent type changes. + +- **isConsentGranted(consentType)** → boolean + - Returns true if the specified consent type is granted. + +- **setDefaultConsentState(consentSettings)** → void + - Pushes a default consent update to the data layer. + +- **updateConsentState(consentSettings)** → void + - Pushes a consent update to the data layer. + +#### Event Handling + +- **addEventCallback(callback)** → void + - Allows you to register a callback function that will be invoked at the end of an event. + +- **callLater(function)** → void + - Schedules a call to a function to occur asynchronously. + +#### Window and Global Object Access + +- **aliasInWindow(toPath, fromPath)** → boolean + - Lets you create an alias (e.g. window.foo = window.bar). + +- **callInWindow(pathToFunction, args)** → * + - Allows you to call functions from a path off the window object, in a policy-controlled way. + +- **copyFromWindow(key)** → * + - Copies a variable from window object. + +- **setInWindow(key, value, overrideExisting)** → boolean + - Sets the given value in window at the given key. + +#### Queue and Array Creation + +- **createArgumentsQueue(fnKey, arrayKey)** → function + - Creates a queue that is populated with argument objects, in support of tag solutions that require it. + +- **createQueue(arrayKey)** → function + - Creates an array in window and returns a function that will push values onto that array. + +#### URI and Encoding + +- **decodeUri(encoded_uri)** → string + - Decodes any encoded characters in the provided URI. + +- **decodeUriComponent(encoded_uri_component)** → string + - Decodes any encoded characters in the provided URI component. + +- **encodeUri(uri)** → string + - Returns an encoded Uniform Resource Identifier (URI) by escaping special characters. + +- **encodeUriComponent(str)** → string + - Returns an encoded Uniform Resource Identifier (URI) by escaping special characters. + +- **fromBase64(base64EncodedString)** → string + - Lets you decode strings from their base64 representation. + +- **toBase64(input)** → string + - Lets you encode a string into a base64 representation. + +#### Utility Functions + +- **generateRandom(min, max)** → number + - Returns a random number (integer) within the given range. + +- **getContainerVersion()** → object + - Returns an object containing data about the current container. + +- **getType(value)** → string + - Returns a string describing the given value's type. + +- **logToConsole(obj1, obj2, ...)** → void + - Logs arguments to the browser console. + +- **makeInteger(value)** → number + - Converts the given value to a number (integer). + +- **makeNumber(value)** → number + - Converts the given value to a number. + +- **makeString(value)** → string + - Returns the given value as a string. + +- **makeTableMap(tableObj, keyColumnName, valueColumnName)** → object + - Converts a simple table object with two columns to a Map. + +- **queryPermission(permission, functionArgs)** → boolean + - Query the allowed and narrowed permissions. + +- **require(name)** → function + - Imports a built-in function by name. + +#### Data Layer Access + +- **copyFromDataLayer(key, dataLayerVersion)** → * + - Returns the value currently assigned to the given key in the data layer. + +- **gtagSet(object)** → void + - Pushes a gtag set command to the data layer. + +#### Cookie Management + +- **getCookieValues(name, decode)** → array + - Returns the values of all cookies with the given name. + +- **setCookie(name, value, options, encode)** → void + - Sets or deletes the cookie with the specified name, value, and options. + +#### URL and Query Parameters + +- **getQueryParameters(queryKey, retrieveAll)** → * + - Returns the first or all of the parameters for the current URL's queryKey. + +- **getUrl(component)** → string + - Returns a string that represents all or a portion of the current URL. + +- **parseUrl(url)** → object + - Returns an object that contains all of a given URL's component parts. + +- **getReferrerUrl(component)** → string + - Reads the document object for the referrer and returns a string that represents a portion of the referrer. + +- **getReferrerQueryParameters(queryKey, retrieveAll)** → * + - Acts the same way as getQueryParameters, except it acts on the referrer instead of the current URL. + +#### Time Management + +- **getTimestamp()** → number + - Returns a number that represents the current time in milliseconds since Unix epoch. + +- **getTimestampMillis()** → number + - Returns a number that represents the current time in milliseconds since Unix epoch. + +#### Network and Injection + +- **injectHiddenIframe(url, onSuccess)** → void + - Adds an invisible iframe to the page. + +- **injectScript(url, onSuccess, onFailure, cacheToken)** → void + - Adds a script tag to the page to load the given URL asynchronously. + +- **sendPixel(url, onSuccess, onFailure)** → void + - Makes a GET request to a specified URL endpoint. + +- **sha256(input, onSuccess, onFailure, options)** → void + - Calculates the SHA-256 digest of the input. + +#### Document Reading + +- **readCharacterSet()** → string + - Returns the value of document.characterSet. + +- **readTitle()** → string + - Returns the value of document.title. + +- **readAnalyticsStorage(cookieOptions)** → object + - Retrieves the data stored for analytics and returns an object with client_id and sessions. + +#### Built-in Objects + +- **JSON** → object + - Returns an object that provides JSON functions. + +- **Math** → object + - An object providing Math functions. + +- **Object** → object + - Returns an object that provides Object methods. + +- **localStorage** → object + - Returns an object with methods for accessing local storage. + +- **templateStorage** → object + - Returns an object with methods for accessing template storage. + +--- + +### Test APIs + +These APIs work with sandboxed JavaScript tests to build tests for custom templates in Google Tag Manager. + +#### Assertions and Validation + +- **assertApi(apiName)** → object + - Returns a matcher object that can be used to fluently make assertions about the given API. + +- **assertThat(actual, opt_message)** → object + - Returns an object that can be used to fluently make assertions about a subject's value. + +- **fail(opt_message)** → void + - Immediately fails the current test and prints the given message, if supplied. + +#### Mocking + +- **mock(apiName, returnValue)** → void + - Allows you to override the behavior of Sandboxed APIs. + +- **mockObject(apiName, objectMock)** → void + - Lets you override the behavior of Sandboxed APIs that return an object. + +#### Test Execution + +- **runCode(data)** → * + - Runs the code for the template in the current test environment. + +--- + +## API Summary + +**Total Core APIs**: 49 functions across 13 categories +**Total Test APIs**: 6 functions across 3 categories +**Total APIs**: 55 functions + +### Core API Categories +1. Consent Management (4 functions) +2. Event Handling (2 functions) +3. Window and Global Object Access (4 functions) +4. Queue and Array Creation (2 functions) +5. URI and Encoding (6 functions) +6. Utility Functions (8 functions) +7. Data Layer Access (2 functions) +8. Cookie Management (2 functions) +9. URL and Query Parameters (5 functions) +10. Time Management (2 functions) +11. Network and Injection (4 functions) +12. Document Reading (3 functions) +13. Built-in Objects (5 functions) + +### Test API Categories +1. Assertions and Validation (3 functions) +2. Mocking (2 functions) +3. Test Execution (1 function) + +--- + +## Usage Examples + +### Working with the Data Layer +```javascript +// Get a value from the data layer +var userId = copyFromDataLayer('userId'); + +// Push a gtag set command +gtagSet({ + 'event': 'page_view', + 'page_title': readTitle() +}); +``` + +### Managing Cookies +```javascript +// Get cookie values +var cookieValues = getCookieValues('tracking_id', true); + +// Set a cookie +setCookie('my_cookie', 'value123', { + 'domain': 'example.com', + 'path': '/', + 'max-age': 86400 +}); +``` + +### Working with URLs +```javascript +// Get URL components +var hostname = getUrl('hostname'); +var pathname = getUrl('pathname'); +var queryString = getUrl('query'); + +// Parse a URL +var urlParts = parseUrl('https://example.com/page?param=value'); + +// Get query parameters +var userParam = getQueryParameters('user_id', false); +``` + +### Async Operations +```javascript +// Inject a script +injectScript('https://example.com/tracker.js', function() { + logToConsole('Script loaded successfully'); +}, function() { + logToConsole('Script failed to load'); +}); + +// Send a pixel +sendPixel('https://example.com/track?event=purchase', function() { + logToConsole('Pixel sent'); +}, function() { + logToConsole('Pixel failed'); +}); + +// Calculate SHA-256 +sha256('input_string', function(result) { + logToConsole('SHA-256: ' + result); +}, function() { + logToConsole('Hash calculation failed'); +}); +``` + +### Consent Management +```javascript +// Check if consent is granted +if (isConsentGranted('analytics_storage')) { + // Proceed with analytics +} + +// Add a consent listener +addConsentListener('analytics_storage', function() { + logToConsole('Consent state changed'); +}); + +// Update consent +updateConsentState({ + 'analytics_storage': 'granted', + 'ad_storage': 'denied' +}); +``` + +### Testing Custom Templates +```javascript +// Mock an API +mock('sendPixel', undefined); + +// Assert API behavior +assertApi('sendPixel').wasCalled(); + +// Run template code with test data +var result = runCode({ + 'event': 'test_event', + 'user_id': '12345' +}); +``` + +--- + +## Reference + +- **Official Documentation**: https://developers.google.com/tag-platform/tag-manager/templates/api +- **Google Tag Manager**: https://tagmanager.google.com +- **GTM Custom Templates Guide**: https://developers.google.com/tag-platform/tag-manager/templates/guide diff --git a/skills/gtm-custom-templates/references/template-examples.md b/skills/gtm-custom-templates/references/template-examples.md new file mode 100644 index 0000000..a2b84e7 --- /dev/null +++ b/skills/gtm-custom-templates/references/template-examples.md @@ -0,0 +1,149 @@ +# Custom Template Examples + + + +## Tag Template Examples + +### Simple Pixel Tag Template + +```javascript +// Code section +const sendPixel = require('sendPixel'); +const encodeUriComponent = require('encodeUriComponent'); +const getUrl = require('getUrl'); + +const pixelUrl = 'https://example.com/pixel?' + + 'page=' + encodeUriComponent(getUrl()) + + '&event=' + encodeUriComponent(data.eventName); + +sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure); +``` + +### HTTP Request Tag Template + +```javascript +// Code section +const sendHttpRequest = require('sendHttpRequest'); +const JSON = require('JSON'); + +const postBody = JSON.stringify({ + event: data.eventName, + userId: data.userId, + timestamp: Date.now() +}); + +const options = { + headers: {'Content-Type': 'application/json'}, + method: 'POST', +}; + +sendHttpRequest(data.endpoint, options, postBody) + .then(data.gtmOnSuccess) + .catch(data.gtmOnFailure); +``` + +## Variable Template Examples + +### Cookie Variable Template + +```javascript +// Code section +const getCookieValues = require('getCookieValues'); +const cookieName = data.cookieName; +const cookies = getCookieValues(cookieName); + +if (cookies.length > 0) { + return cookies[0]; +} + +return data.defaultValue; +``` + +### LocalStorage Variable Template + +```javascript +// Code section +const localStorage = require('localStorage'); +const key = data.localStorageKey; + +return localStorage.getItem(key) || data.defaultValue; +``` + +### Custom JavaScript Function Variable + +```javascript +// Code section +const makeTableMap = require('makeTableMap'); +const lookupTable = makeTableMap(data.tableInput, 'input', 'output'); + +const inputValue = data.inputVariable; +return lookupTable[inputValue] || data.defaultValue; +``` + +## Common Patterns + +### Error Handling + +```javascript +const sendHttpRequest = require('sendHttpRequest'); +const logToConsole = require('logToConsole'); + +sendHttpRequest(url, options) + .then(response => { + logToConsole('Success:', response); + data.gtmOnSuccess(); + }) + .catch(error => { + logToConsole('Error:', error); + data.gtmOnFailure(); + }); +``` + +### Conditional Logic + +```javascript +const getType = require('getType'); + +if (getType(data.value) === 'undefined') { + return data.defaultValue; +} + +if (data.value > 0) { + return 'positive'; +} else if (data.value < 0) { + return 'negative'; +} else { + return 'zero'; +} +``` + +### Data Validation + +```javascript +const makeNumber = require('makeNumber'); +const makeString = require('makeString'); + +// Validate and convert input +const numericValue = makeNumber(data.inputValue); + +if (numericValue === undefined) { + logToConsole('Invalid numeric input'); + data.gtmOnFailure(); + return; +} + +// Continue with validated value +const result = numericValue * 2; +data.gtmOnSuccess(); +``` + +## Placeholder for Additional Examples + +This file will be enhanced with: +- Server-side client template examples +- Complex permission configurations +- Field validation patterns +- Real-world use cases +- Community template patterns + +**Status**: ⚠️ To be enhanced with documentation extraction diff --git a/skills/gtm-custom-templates/references/template-testing.md b/skills/gtm-custom-templates/references/template-testing.md new file mode 100644 index 0000000..f535bca --- /dev/null +++ b/skills/gtm-custom-templates/references/template-testing.md @@ -0,0 +1,96 @@ +# Tests + +**Source**: https://developers.google.com/tag-platform/tag-manager/templates/tests +**Extracted**: 2025-01-09 + +## Overview + +Unit tests for Google Tag Manager custom templates help you validate the functionality of your templates. You can create a set of tests for each template that can be run without needing to deploy your tag, which allows you to continuously test your template's behavior during development. Each test can provide sample input values, mock function calls, and assert code behavior. + +## Limitations + +- Unit tests do not check validation rules but you can manually check validation using the **Run Code** button. +- Permission checks do not happen on mocked APIs in unit tests. + +## Step-by-Step Guide: Creating a Variable Template with Tests + +This guide creates a variable template that takes an input string and returns the uppercase version of that string. + +### Steps 1-3: Create Template and Add Field +1. Create a new variable template. Click **Templates** in the left navigation and click **New** under the **Variable Templates** section. +2. Click **Fields**. +3. Click **Add Field** and select **Text input**. Name the field \`text1\` and set the display name to _"Text 1"_. + +### Step 4: Add Template Code + +In the **Code** tab, replace the default code with this sandboxed JavaScript: + +\`\`\`javascript +let input = data.text1; +return input.toUpperCase(); +\`\`\` + +### Steps 5-7: Create First Test + +5. Click **Tests** to open the testing tab. +6. Click **Add Test** and change the test's name from _"Untitled test 1"_ to _"Handles strings"_. +7. Click on the expand icon to reveal the test's sandboxed JavaScript editor. Replace the code with: + +\`\`\`javascript +// Call runCode to run the template's code with a lowercase string +let variableResult = runCode({text1: 'this is a test'}); +// Validate that the result of runCode is an uppercase string. +assertThat(variableResult).isEqualTo('THIS IS A TEST'); +\`\`\` + +This test passes the string \`'this is a test'\` to the variable and verifies that the variable returns the expected value of \`'THIS IS A TEST'\`. The \`runCode\` API is used to run the template code in the **Code** tab. The argument to \`runCode\` is an object that is used as the data global. The \`assertThat\` API returns an object that can be used to fluently make assertions about a subject's value. + +### Step 8: Run Tests + +Click **▶ Run Tests** to run the test. The output of the test will appear in the Console. + +The **▶ Run Tests** button runs all of the enabled tests in the template, in the order shown. To change the order, use the drag icon. A test can be temporarily enabled or disabled by clicking on the circle to the left of the test name. To run a single test, click the ▶ button that appears when you move the mouse over the test. + +The console should print the total number of tests run and the number of tests that failed, if any. In this case, only one test was run and it should pass. + +### Steps 9-11: Create Second Test for Edge Cases + +9. Click **Add Test** again to add a second test. Change the test's name from _"Untitled test 2"_ to _"Handles undefined"_. +10. Click on the test to expand it and reveal the sandboxed JavaScript editor. Enter: + +\`\`\`javascript +let variableResult = runCode({}); +assertThat(variableResult).isEqualTo(undefined); +\`\`\` + +11. Click **▶ Run Tests** to run all of the tests at once. The output of the test will appear in the console. + +The _Handles undefined_ test should fail. Congratulations, you found a bug! + +### Steps 12-14: Fix Code and Re-test + +12. Click **Code** to go back and edit the template's sandboxed JavaScript code. Update the code as follows: + +\`\`\`javascript +const getType = require('getType'); + +let input = data.text1; +if (getType(input) !== 'string') { + return input; +} +return input.toUpperCase(); +\`\`\` + +The updated code follows the best practice of validating the \`input\` variable before using it. + +13. Click **Tests** to go back to the list of test cases. +14. Click **▶ Run Tests** to run all of the test cases again. This time the _Handles undefined_ test should pass. +15. Click **Save**, and close the Template Editor. + +## Core APIs + +### runCode +Executes the template's code with provided sample data object. Arguments are merged into the data global variable used in the template code. + +### assertThat +Returns an object that can be used to fluently make assertions about a subject's value. Used for validation in tests. diff --git a/skills/gtm-datalayer/SKILL.md b/skills/gtm-datalayer/SKILL.md new file mode 100644 index 0000000..a264b08 --- /dev/null +++ b/skills/gtm-datalayer/SKILL.md @@ -0,0 +1,474 @@ +--- +name: gtm-datalayer +description: Expert guidance for implementing the GTM data layer including structure, events, e-commerce tracking, SPA patterns, and best practices. Use when designing data layer architecture, implementing tracking events, setting up e-commerce data, debugging data layer issues, migrating to GA4 data layer patterns, working with dataLayer.push syntax, implementing data layers in React, Vue, Angular, or Next.js applications, or working with .js, .jsx, or .tsx files containing ecommerce objects. +--- + +# GTM Data Layer Expert + +## Overview + +This skill provides comprehensive expertise for implementing and managing the Google Tag Manager data layer, the foundational data structure that powers GTM implementations. Use this skill for data layer architecture, event tracking patterns, e-commerce implementation, single-page application (SPA) strategies, and data layer best practices. + +## When to Use This Skill + +Invoke this skill when: +- Designing data layer architecture for a website or app +- Implementing custom events and event tracking +- Setting up e-commerce tracking (GA4 or enhanced e-commerce) +- Building data layer for single-page applications (React, Vue, Angular) +- Debugging data layer issues or undefined variables +- Migrating from Universal Analytics to GA4 data layer schema +- Establishing data layer naming conventions and standards +- Creating data layer documentation +- Validating data layer implementation +- Troubleshooting timing or data quality issues + +## Data Layer Fundamentals + +### What is the Data Layer? + +The data layer is a JavaScript object that temporarily stores data before passing it to GTM. It acts as a structured data repository that separates your website's data from your tag management implementation. + +**Basic Structure:** +```javascript +window.dataLayer = window.dataLayer || []; +``` + +### Data Layer Initialization + +Always initialize the data layer BEFORE the GTM container snippet: + +```javascript + + + + + +``` + +### Pushing to the Data Layer + +Use `dataLayer.push()` to add data: + +```javascript +// Basic push +dataLayer.push({ + 'event': 'custom_event', + 'event_category': 'engagement', + 'event_label': 'button_click' +}); + +// E-commerce push +dataLayer.push({ + 'event': 'purchase', + 'ecommerce': { + 'transaction_id': 'T12345', + 'value': 99.99, + 'currency': 'USD', + 'items': [ + { + 'item_id': 'SKU123', + 'item_name': 'Product Name', + 'price': 99.99, + 'quantity': 1 + } + ] + } +}); +``` + +## Common Data Layer Patterns + +### Page Metadata + +```javascript +dataLayer.push({ + 'page_type': 'product', + 'page_category': 'electronics', + 'page_language': 'en', + 'page_region': 'US' +}); +``` + +### User Information + +```javascript +dataLayer.push({ + 'user_id': 'hashed_user_id', // Never use PII! + 'user_logged_in': true, + 'user_type': 'premium', + 'user_ltv_segment': 'high' +}); +``` + +### Custom Events + +```javascript +// Form submission +dataLayer.push({ + 'event': 'form_submission', + 'form_id': 'contact_form', + 'form_name': 'Contact Us' +}); + +// Video interaction +dataLayer.push({ + 'event': 'video_progress', + 'video_title': 'Product Demo', + 'video_percent': 50 +}); + +// Search +dataLayer.push({ + 'event': 'search', + 'search_term': 'running shoes', + 'search_results': 42 +}); +``` + +### Error Tracking + +```javascript +dataLayer.push({ + 'event': 'error', + 'error_type': '404', + 'error_message': 'Page not found', + 'error_url': window.location.href +}); +``` + +## E-commerce Data Layer + +### GA4 E-commerce Events + +**View Item List:** +```javascript +dataLayer.push({ + 'event': 'view_item_list', + 'ecommerce': { + 'items': [ + { + 'item_id': 'SKU123', + 'item_name': 'Product Name', + 'item_brand': 'Brand', + 'item_category': 'Category', + 'price': 29.99, + 'quantity': 1 + } + ] + } +}); +``` + +**Add to Cart:** +```javascript +dataLayer.push({ + 'event': 'add_to_cart', + 'ecommerce': { + 'currency': 'USD', + 'value': 29.99, + 'items': [ + { + 'item_id': 'SKU123', + 'item_name': 'Product Name', + 'price': 29.99, + 'quantity': 1 + } + ] + } +}); +``` + +**Purchase:** +```javascript +dataLayer.push({ + 'event': 'purchase', + 'ecommerce': { + 'transaction_id': 'T12345', + 'value': 99.99, + 'tax': 8.00, + 'shipping': 5.00, + 'currency': 'USD', + 'items': [ + { + 'item_id': 'SKU123', + 'item_name': 'Product Name', + 'item_brand': 'Brand', + 'item_category': 'Electronics', + 'price': 29.99, + 'quantity': 3 + } + ] + } +}); +``` + +### E-commerce Implementation Workflow + +1. **Product Listing Pages**: Push `view_item_list` with all visible products +2. **Product Clicks**: Push `select_item` when user clicks product +3. **Product Detail Pages**: Push `view_item` for the viewed product +4. **Add to Cart**: Push `add_to_cart` when item added +5. **Checkout Steps**: Push `begin_checkout`, `add_shipping_info`, `add_payment_info` +6. **Purchase**: Push `purchase` on order confirmation page + +## Single-Page Application (SPA) Data Layer + +### Virtual Pageview Pattern + +```javascript +// On route change in React/Vue/Angular +function trackVirtualPageview(pageData) { + // Clear previous page data + dataLayer.push({ + page_title: undefined, + page_type: undefined, + page_category: undefined + }); + + // Push new page data + dataLayer.push({ + 'event': 'virtual_pageview', + 'page_title': pageData.title, + 'page_location': pageData.url, + 'page_type': pageData.type + }); +} +``` + +### React Example + +```javascript +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +function usePageTracking() { + const location = useLocation(); + + useEffect(() => { + dataLayer.push({ + 'event': 'virtual_pageview', + 'page_path': location.pathname, + 'page_title': document.title + }); + }, [location]); +} +``` + +### Next.js Example + +```javascript +// pages/_app.js +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; + +export default function App({ Component, pageProps }) { + const router = useRouter(); + + useEffect(() => { + const handleRouteChange = (url) => { + dataLayer.push({ + 'event': 'virtual_pageview', + 'page_path': url, + 'page_title': document.title + }); + }; + + router.events.on('routeChangeComplete', handleRouteChange); + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router.events]); + + return ; +} +``` + +## Data Layer Best Practices + +### Naming Conventions + +✅ **Good:** +```javascript +dataLayer.push({ + 'event': 'form_submit', + 'form_id': 'contact_form', + 'user_logged_in': true +}); +``` + +❌ **Bad:** +```javascript +dataLayer.push({ + 'event': 'formSubmit', // Inconsistent casing + 'FormID': 'contact_form', // PascalCase + 'logged in': true // Spaces +}); +``` + +**Conventions:** +- Use snake_case for consistency +- Use descriptive names +- Avoid abbreviations unless standard +- Be consistent across all properties + +### Data Structure + +**Flat structure for simple data:** +```javascript +dataLayer.push({ + 'event': 'custom_event', + 'event_category': 'engagement', + 'event_action': 'click', + 'event_label': 'cta_button' +}); +``` + +**Nested structure for complex data:** +```javascript +dataLayer.push({ + 'event': 'product_view', + 'product': { + 'id': 'SKU123', + 'name': 'Product Name', + 'price': 29.99, + 'category': 'Electronics' + } +}); +``` + +### Timing Considerations + +1. Initialize data layer BEFORE GTM snippet +2. Push critical page data before GTM loads +3. Push event-specific data when events occur +4. For SPAs, handle route changes properly +5. Consider async data loading + +### Security and Privacy + +**❌ NEVER include PII in data layer:** +```javascript +// BAD - Do not do this! +dataLayer.push({ + 'user_email': 'user@example.com', + 'user_name': 'John Doe', + 'credit_card': '1234-5678-9012-3456' +}); +``` + +**✅ Use hashed or anonymized IDs:** +```javascript +// GOOD +dataLayer.push({ + 'user_id': 'hashed_user_id_123abc', + 'user_segment': 'premium' +}); +``` + +### Data Quality + +- Always validate required fields exist +- Use consistent data types (string, number, boolean) +- Provide default values when appropriate +- Handle null/undefined gracefully +- Test thoroughly across user journeys + +## Debugging Data Layer + +### Browser Console Inspection + +```javascript +// View entire data layer +console.log(window.dataLayer); + +// View specific push +dataLayer.push({ + 'event': 'test_event', + 'test_data': 'value' +}); +console.log(dataLayer[dataLayer.length - 1]); + +// Watch for pushes +const originalPush = dataLayer.push; +dataLayer.push = function() { + console.log('DataLayer push:', arguments[0]); + return originalPush.apply(dataLayer, arguments); +}; +``` + +### GTM Preview Mode + +1. Enter Preview mode in GTM +2. Navigate to your site +3. Click on any event in Summary +4. View "Data Layer" tab +5. Inspect all pushed values + +### Common Issues + +**Undefined Variable:** +- Check data layer path (dot notation) +- Verify timing (pushed before tag fires?) +- Check for typos in variable names + +**Data Not Persisting:** +- Data layer is event-based, not state-based +- Variables don't persist across pages (unless SPAs) +- Must re-push data on each page load + +**Timing Issues:** +- Ensure data layer initialized before GTM +- Check async loading issues +- Verify event fires before tag needs data + +## References + +This skill includes comprehensive reference documentation: + +- **references/datalayer-fundamentals.md** - Data layer basics, syntax, and implementation +- **references/ecommerce-datalayer.md** - Complete e-commerce event patterns +- **references/spa-datalayer.md** - Single-page application implementation +- **references/datalayer-best-practices.md** - Naming, structure, security, testing + +Search reference files for specific patterns: +```bash +grep -r "purchase" references/ +grep -r "React" references/ +grep -r "consent" references/ +``` + +## Integration with Other Skills +## Integration with Other Skills + +- **gtm-general** - General GTM guidance and concepts +- **gtm-setup** - GTM container setup and installation +- **gtm-tags** - Configure tags that use data layer variables +- **gtm-triggers** - Set up custom event triggers from data layer +- **gtm-variables** - Create data layer variables to access values +- **gtm-debugging** - Debug data layer implementation issues +- **gtm-custom-templates** - Build custom templates that read data layer +- **gtm-api** - Programmatic data layer documentation export +- **gtm-best-practices** - Data layer naming and structure best practices + +## Quick Reference +## Quick Reference + +**Initialize:** `window.dataLayer = window.dataLayer || [];` + +**Push Event:** `dataLayer.push({'event': 'event_name', 'key': 'value'});` + +**Clear Variable:** `dataLayer.push({'variable_name': undefined});` + +**E-commerce:** Always use GA4 schema with `ecommerce` object + +**SPAs:** Clear previous data and push virtual pageviews + +**Security:** Never push PII - use hashed IDs only diff --git a/skills/gtm-datalayer/references/datalayer-best-practices.md b/skills/gtm-datalayer/references/datalayer-best-practices.md new file mode 100644 index 0000000..6b1392f --- /dev/null +++ b/skills/gtm-datalayer/references/datalayer-best-practices.md @@ -0,0 +1,572 @@ +# Data Layer Best Practices + +## Overview + +The data layer is a JavaScript object used by Google Tag Manager and gtag.js to pass information to tags. Events or variables can be passed via the data layer, and triggers can be set up based on the values of variables. This guide covers best practices for implementing and maintaining the data layer. + +## Installation and Setup + +### Proper Data Layer Initialization + +The data layer must be established before the Tag Manager or Google tag snippet loads: + +```html + + + + +``` + +### Gtag.js Installation + +For gtag.js implementations: + +```html + + + +``` + +## How Data Layer Information is Processed + +Tag Manager processes data layer messages on a first-in, first-out (FIFO) basis: + +1. Each message is processed one at a time, in the order received +2. If a message is an event, any tags with trigger conditions that have been met will fire before Tag Manager moves to the next message +3. `gtag()` or `dataLayer.push()` calls queue messages for processing after all pending messages +4. Updated data layer values are not guaranteed to be available for the next event + +### Event Handling Pattern + +To ensure data availability, add an event name to messages and listen for that event with a Custom Event trigger: + +```javascript +// Push data with a custom event +dataLayer.push({ + 'event': 'userData', + 'userId': '12345', + 'userType': 'premium' +}); +``` + +## Best Practices + +### 1. Never Overwrite the dataLayer Variable + +**Bad:** + +```javascript +// This overwrites existing values! +dataLayer = [{'item': 'value'}]; +``` + +**Good:** + +```javascript +// Initialize at the top of the page +window.dataLayer = window.dataLayer || []; + +// Then use push() to add values +dataLayer.push({'item': 'value'}); +``` + +### 2. Use Correct Casing + +The `dataLayer` object name is case-sensitive: + +**Bad:** + +```javascript +datalayer.push({'pageTitle': 'Home'}); // lowercase 'l' +DataLayer.push({'pageTitle': 'Home'}); // capital 'D' +``` + +**Good:** + +```javascript +dataLayer.push({'pageTitle': 'Home'}); // correct camelCase +``` + +### 3. Use Proper Quote Marks + +All data layer variable names should be enclosed in quotes: + +**Bad:** + +```javascript +dataLayer.push({new-variable: 'value'}); // No quotes +dataLayer.push({newVariable: 'value'}); // No quotes (though valid JS) +``` + +**Good:** + +```javascript +dataLayer.push({'new-variable': 'value'}); // Proper quotes +dataLayer.push({'newVariable': 'value'}); // Proper quotes +``` + +### 4. Keep Variable Names Consistent Across Pages + +Use consistent naming conventions for the same concept across all pages: + +**Bad:** + +```javascript +// Homepage: +dataLayer.push({'visitorType': 'low-value'}); + +// Checkout Page: +dataLayer.push({'visitor_type': 'high-value'}); // Different naming! +``` + +**Good:** + +```javascript +// Homepage: +dataLayer.push({'visitorType': 'low-value'}); + +// Checkout Page: +dataLayer.push({'visitorType': 'high-value'}); // Consistent naming +``` + +## Using the Data Layer with Event Handlers + +### Sending Events + +Use the `event` key to initiate sending events: + +```javascript +dataLayer.push({'event': 'event_name'}); +``` + +### Event with Button Click + +```html + +``` + +### Dynamic Data Layer Variables + +Push variables dynamically to capture information: + +```javascript +dataLayer.push({'variable_name': 'variable_value'}); +``` + +#### Example: Form Value Capture + +```javascript +// Capture color selection +dataLayer.push({'color': 'red'}); +``` + +## Advanced Patterns + +### Pushing Multiple Variables and Events + +You can push multiple variables and events simultaneously: + +```javascript +dataLayer.push({ + 'color': 'red', + 'conversionValue': 50, + 'event': 'customize' +}); +``` + +### Persisting Data Layer Variables Across Pages + +To persist data layer variables between pages: + +1. Call `dataLayer.push()` on each page load after data layer instantiation +2. Place the push above the GTM container code for immediate availability + +```html + + + + +``` + +**Important:** Variables persist only as long as the visitor remains on the current page. Variables relevant across pages must be declared in the data layer on each page. + +## Custom Data Layer Methods + +### The set() Method + +Set values to retrieve later: + +```javascript +window.dataLayer.push(function() { + this.set('time', new Date()); +}); +``` + +### The get() Method + +Retrieve values that were set: + +```javascript +window.dataLayer.push(function() { + const existingTime = this.get('time'); + if (existingTime !== null) { + // Value exists, use it + } else { + // Value doesn't exist + } +}); +``` + +### The reset() Method + +Reset the data in the data layer (useful for single-page applications): + +```javascript +window.dataLayer.push(function() { + this.reset(); +}); +``` + +## Renaming the Data Layer + +### For gtag.js + +Add a query parameter named "l" to set a new data layer name: + +```html + + + +``` + +### For Tag Manager + +Replace the data layer parameter value in the container snippet: + +```html + + + +``` + +Update all references to match the new name: + +```javascript + +``` + +## Common Data Layer Patterns + +### Page View Data + +```javascript +dataLayer.push({ + 'event': 'pageview', + 'page': { + 'path': '/products/shoes', + 'title': 'Shoes | My Store', + 'category': 'Products' + }, + 'user': { + 'id': 'USER123', + 'status': 'logged_in', + 'type': 'premium' + } +}); +``` + +### User Interaction + +```javascript +dataLayer.push({ + 'event': 'button_click', + 'element': { + 'id': 'cta-button', + 'text': 'Get Started', + 'destination': '/signup' + } +}); +``` + +### Form Submission + +```javascript +dataLayer.push({ + 'event': 'form_submission', + 'form': { + 'id': 'contact-form', + 'name': 'Contact Us', + 'fields': 5 + } +}); +``` + +### Video Tracking + +```javascript +dataLayer.push({ + 'event': 'video_start', + 'video': { + 'title': 'Product Demo', + 'duration': 120, + 'provider': 'youtube', + 'url': 'https://youtube.com/watch?v=xxx' + } +}); +``` + +## Troubleshooting + +### Common Errors + +#### 1. Overwriting dataLayer + +**Problem:** Using direct assignment instead of push + +```javascript +// Wrong +dataLayer = [{'item': 'value'}]; +``` + +**Solution:** Always use push after initialization + +```javascript +// Correct +dataLayer.push({'item': 'value'}); +``` + +#### 2. Case Sensitivity + +**Problem:** Incorrect casing + +```javascript +datalayer.push({'pageTitle': 'Home'}); // Wrong +``` + +**Solution:** Use correct camelCase + +```javascript +dataLayer.push({'pageTitle': 'Home'}); // Correct +``` + +#### 3. Missing Quotes + +**Problem:** Variable names without quotes + +```javascript +dataLayer.push({pageTitle: 'Home'}); // Can cause issues +``` + +**Solution:** Always use quotes + +```javascript +dataLayer.push({'pageTitle': 'Home'}); // Better +``` + +#### 4. Inconsistent Variable Names + +**Problem:** Using different names for the same concept + +```javascript +// Page 1 +dataLayer.push({'user_type': 'premium'}); + +// Page 2 +dataLayer.push({'userType': 'premium'}); +``` + +**Solution:** Document and enforce naming conventions + +```javascript +// All pages +dataLayer.push({'userType': 'premium'}); +``` + +## Data Layer Structure Best Practices + +### Use Nested Objects for Organization + +```javascript +dataLayer.push({ + 'event': 'purchase', + 'transaction': { + 'id': 'T12345', + 'revenue': 99.99, + 'tax': 9.99, + 'shipping': 5.00 + }, + 'customer': { + 'id': 'C67890', + 'type': 'returning', + 'lifetime_value': 1500.00 + } +}); +``` + +### Use Arrays for Multiple Similar Items + +```javascript +dataLayer.push({ + 'event': 'product_impressions', + 'products': [ + {'id': 'P1', 'name': 'Blue Shoes', 'price': 49.99}, + {'id': 'P2', 'name': 'Red Shoes', 'price': 59.99}, + {'id': 'P3', 'name': 'Green Shoes', 'price': 54.99} + ] +}); +``` + +### Use Clear, Descriptive Names + +**Bad:** + +```javascript +dataLayer.push({ + 'e': 'clk', + 'v': '100', + 't': 'btn' +}); +``` + +**Good:** + +```javascript +dataLayer.push({ + 'event': 'button_click', + 'value': '100', + 'element_type': 'button' +}); +``` + +## Testing and Validation + +### 1. Use Debug Mode + +Enable debug mode in Tag Manager to verify data layer pushes in real-time. + +### 2. Console Logging + +Check data layer contents in the browser console: + +```javascript +console.log(window.dataLayer); +``` + +### 3. Data Layer Checker Extensions + +Use browser extensions like: + +- Google Tag Assistant +- dataLayer Checker +- Tag Manager/Analytics Debugger + +### 4. Preview Mode + +Always test changes in Tag Manager Preview mode before publishing. + +## Performance Considerations + +### 1. Push Data Before It's Needed + +Push data layer variables before the tags that need them fire. + +### 2. Avoid Excessive Pushes + +Consolidate data into single pushes when possible: + +**Bad:** + +```javascript +dataLayer.push({'userId': '123'}); +dataLayer.push({'userType': 'premium'}); +dataLayer.push({'event': 'user_data'}); +``` + +**Good:** + +```javascript +dataLayer.push({ + 'userId': '123', + 'userType': 'premium', + 'event': 'user_data' +}); +``` + +### 3. Don't Push Unnecessarily Large Data + +Avoid pushing very large arrays or objects that won't be used. + +### 4. Clear Data for SPAs + +For single-page applications, reset the data layer between virtual page views: + +```javascript +window.dataLayer.push(function() { + this.reset(); +}); +``` + +## Documentation + +### Maintain a Data Layer Specification + +Document all data layer variables: + +- Variable name +- Data type +- When it's populated +- Example values +- Pages where it appears +- Tags/triggers that use it + +### Example Documentation Format + +```markdown +## userType + +- **Type:** String +- **Values:** 'guest', 'registered', 'premium' +- **Populated:** On all pages after user identification +- **Example:** `{'userType': 'premium'}` +- **Used by:** User Segmentation Tags, Personalization Triggers +``` + +## Resources + +- [Google Tag Manager Data Layer Documentation](https://developers.google.com/tag-platform/tag-manager/datalayer) +- [Data Layer Best Practices (Google Support)](https://support.google.com/tagmanager/answer/6164391) +- [GTM Help Center](https://support.google.com/tagmanager) diff --git a/skills/gtm-datalayer/references/datalayer-fundamentals.md b/skills/gtm-datalayer/references/datalayer-fundamentals.md new file mode 100644 index 0000000..5277027 --- /dev/null +++ b/skills/gtm-datalayer/references/datalayer-fundamentals.md @@ -0,0 +1,258 @@ +# The Data Layer + +**Source**: https://developers.google.com/tag-platform/tag-manager/datalayer +**Extracted**: 2025-01-09 + +The data layer is an object used by Google Tag Manager and gtag.js to pass information to tags. Events or variables can be passed via the data layer, and triggers can be set up based on the values of variables. + +For example, if you fire a remarketing tag when the value of `purchase_total` is greater than $100, or based on the specific events, e.g. when a button is clicked, your data layer can be configured to make that data available to your tags. The data layer object is structured as JSON. For example: + +```json +{ + "event": "checkout_button", + "gtm": { + "uniqueEventId": 2, + "start": 1639524976560, + "scrollThreshold": 90, + "scrollUnits": "percent", + "scrollDirection": "vertical", + "triggers": "1_27" + }, + "value": "120" +} +``` + +Google tags are designed to easily reference information that is added to the data layer in an organized and predictable way, rather than by parsing variables, transaction information, page categories, and other signals scattered throughout your page. A data layer implementation populated with variables and associated values will help to ensure that relevant data is available when your tags need them. + +## Installation + +For Tag Manager web page installations, you must create a data layer. The data layer is established before Tag Manager is loaded: + +```javascript + + + + +``` + +In standard gtag.js implementations where the tag has been copied from within the product and added to a web page, the code to establish the data layer is provided. In custom implementations of the Google tag, add the data layer code at the beginning of your script: + +```javascript + + + +``` + +## How Data Layer Information is Processed + +When a container is loaded, Tag Manager will begin to process all queued data layer push messages. Tag Manager processes messages on a first-in, first-out basis: Each message is processed one at a time, in the order it was received. If the message is an event, any tags with trigger conditions that have been met will fire before Tag Manager moves on to the next message. + +If a `gtag()` or `dataLayer.push()` call is made by code on a page, in a Custom Template, or in a Custom HTML tag, the associated message is queued and processed after all other pending messages are evaluated. This means that any updated data layer values are not guaranteed to be available for the next event. To handle these cases, you should add an event name to a message as it is pushed to the data layer, and then listen for that event name with a Custom Event trigger. + +## Use a Data Layer with Event Handlers + +The `dataLayer` object uses an `event` command to initiate the sending of events. + +The Google tag and Tag Manager use a special data layer variable called `event` that is used by JavaScript event listeners to fire tags when a user interacts with website elements. For example, you may want to fire a conversion tracking tag when a user clicks a purchase confirmation button. Events may be called whenever a user interacts with website elements such as links, buttons, scrolls, etc. + +This functionality is accomplished by calling `dataLayer.push()` when an event occurs. The syntax for sending an event with `dataLayer.push()` is as follows: + +```javascript +dataLayer.push({'event': 'event_name'}); +``` + +Where `event_name` is a string that describes the event, such as `'login'`, `purchase`, or `search`. + +Use `dataLayer.push()` to send event data when an action occurs that you'd like to measure. For example, to send an event when a user clicks a button, modify the button's `onclick` handler: + +```html + +``` + +You can push data layer variables to the data layer dynamically to capture information such as values entered or selected in a form, metadata associated with a video that the visitor is playing, the color of a product (e.g. a car) customized by the visitor, the destination URLs of clicked links, etc. + +The basic syntax for setting dynamic data layer variables is as follows: + +```javascript +dataLayer.push({'variable_name': 'variable_value'}); +``` + +Where `'variable_name'` is a string indicating the name of the data layer variable to be set, and `'variable_value'` is a string indicating the value of the data layer variable to be set or replaced. + +For example, to set a data layer variable with a color preference when a visitor engages with a product customization tool: + +```javascript +dataLayer.push({'color': 'red'}); +``` + +## One Push, Multiple Variables + +You can push multiple variables and events at once: + +```javascript +dataLayer.push({ + 'color': 'red', + 'conversionValue': 50, + 'event': 'customize' +}); +``` + +## Persist Data Layer Variables + +To persist data layer variables between web pages, call `dataLayer.push()` after the data layer has been instantiated on each page load, and push the variables to the data layer. If you want these data layer variables to be available to Tag Manager when the container is loaded, add a `dataLayer.push()` call above the Tag Manager container code: + +```javascript + + + + +``` + +Each variable declared within the data layer object will persist only as long as the visitor remains on the current page. Data layer variables that are relevant across pages (e.g. `visitorType`) must be declared in the data layer on each page of your website. While you don't need to put the same set of variables in the data layer on every page, you should use a consistent naming convention. In other words: if you set the page category on the signup page using a variable called `pageCategory`, your product and purchase pages should use the `pageCategory` variable as well. + +## Troubleshooting + +Here are some data layer troubleshooting tips: + +**Do not overwrite the `window.dataLayer` variable:** When you use the data layer directly (e.g. `dataLayer = [{'item': 'value'}]`), it will overwrite any existing values in the `dataLayer`. Tag Manager installations should instantiate the data layer as high up in the source code as possible, above the container snippet, using `window.dataLayer = window.dataLayer || [];`. After the `dataLayer` has been declared, use `dataLayer.push({'item': 'value'})` to add additional values to it, and if those values need to be available to Tag Manager when the page loads, then that `dataLayer.push()` call needs to be above the Tag Manager container code as well. + +**The `dataLayer` object name is case-sensitive:** If you try to push a variable or event without the proper casing, the push will not work. + +```javascript +datalayer.push({'pageTitle': 'Home'}); // Bad (datalayer in lowercase) +dataLayer.push({'pageTitle': 'Home'}); // Good (dataLayer in camel case) +``` + +`dataLayer.push` must be called with valid JavaScript objects. All data layer variable names should be enclosed in quotes. + +```javascript +dataLayer.push({new-variable: 'value'}); // Bad - no quote marks +dataLayer.push({'new-variable': 'value'}); // Good - proper quote marks +``` + +**Keep variable names consistent across pages:** If you use different variable names for the same concept on different pages, your tags will be unable to consistently fire in all desired locations. + +Bad: +```javascript +// Homepage: +dataLayer.push({'visitorType': 'low-value'}); + +// Checkout Page: +dataLayer.push({'visitor_type': 'high-value'}); +``` + +Good: +```javascript +// Homepage: +dataLayer.push({'visitorType': 'low-value'}); + +// Checkout Page: +dataLayer.push({'visitorType': 'high-value'}); +``` + +## Rename the Data Layer + +The default name of the data layer object initiated by the Google tag or Tag Manager is `dataLayer`. If you'd prefer to use a different name for your data layer, you may do so by editing the data layer parameter value in your Google tag or Tag Manager container snippet with the name of your choice. + +### For gtag.js + +Add a query parameter named "l" to the URL to set the new data layer name, e.g. `l=myNewName`. Update all instances of `dataLayer` in the Google tag snippet to the new name: + +```javascript + + + +``` + +### For Tag Manager + +Replace the data layer parameter value in your container snippet with the name of your choice: + +```javascript + + + +``` + +Once renamed, all references to your data layer (i.e. when declaring the data layer above the snippet, or when pushing events or dynamic data layer variables to the data layer with the `.push()` command) must be adjusted to reflect your custom data layer name: + +```javascript + +``` + +## Custom Data Layer Methods + +If you push a function to the data layer, it will be invoked with this set to an abstract data model. This abstract data model can get and set values to a key value store, and also provides a way to reset the data layer. + +### Set + +The `set` function on the abstract data model lets you set values to retrieve through get: + +```javascript +window.dataLayer.push(function() { + this.set('time', new Date()); +}); +``` + +### Get + +The `get` function on the abstract data model lets you retrieve values that were set: + +```javascript +window.dataLayer.push(function() { + const existingTime = this.get('time'); + if (existingTime !== null) { + // Change behavior based on whether or not this value exists... + } else { + // ... + } +}) +``` + +### Reset + +The `reset` function on the abstract data model lets you reset the data in the data layer. This is most useful with a page that will remain open and the data layer size continues to grow over time: + +```javascript +window.dataLayer.push(function() { + this.reset(); +}) +``` diff --git a/skills/gtm-datalayer/references/ecommerce-datalayer.md b/skills/gtm-datalayer/references/ecommerce-datalayer.md new file mode 100644 index 0000000..2fdc8ff --- /dev/null +++ b/skills/gtm-datalayer/references/ecommerce-datalayer.md @@ -0,0 +1,542 @@ +# E-commerce Data Layer Patterns + +## Overview + +Ecommerce events in Google Analytics 4 (GA4) enable you to collect information about shopping behavior and quantify your most popular products, measure the influence of promotions and product placement on revenue. + +## E-commerce Object Structure + +### GA4 E-commerce Schema + +All ecommerce events use an `items` array to represent products and services. The items array can include up to 200 elements and supports up to 27 custom parameters in addition to prescribed parameters. + +#### Item Object Structure + +```javascript +{ + item_id: "SKU_12345", // Required + item_name: "Stan and Friends Tee", // Required + affiliation: "Google Merchandise Store", + coupon: "SUMMER_FUN", + discount: 2.22, + index: 0, + item_brand: "Google", + item_category: "Apparel", + item_category2: "Adult", + item_category3: "Shirts", + item_category4: "Crew", + item_category5: "Short sleeve", + item_list_id: "related_products", + item_list_name: "Related Products", + item_variant: "green", + location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo", + price: 10.01, + quantity: 3 +} +``` + +## E-commerce Events + +### 1. View Item List + +When a user is presented with a list of results (e.g., search results, category page), send a `view_item_list` event: + +```javascript +gtag("event", "view_item_list", { + item_list_id: "related_products", + item_list_name: "Related products", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + affiliation: "Google Merchandise Store", + coupon: "SUMMER_FUN", + discount: 2.22, + index: 0, + item_brand: "Google", + item_category: "Apparel", + item_category2: "Adult", + item_category3: "Shirts", + item_category4: "Crew", + item_category5: "Short sleeve", + item_list_id: "related_products", + item_list_name: "Related Products", + item_variant: "green", + location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo", + price: 10.01, + quantity: 3 + } + ] +}); +``` + +### 2. Select Item + +When a user selects an item from the list: + +```javascript +gtag("event", "select_item", { + item_list_id: "related_products", + item_list_name: "Related products", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + // ... other item parameters + } + ] +}); +``` + +### 3. View Item Details + +Measure how many times item details are viewed: + +```javascript +gtag("event", "view_item", { + currency: "USD", + value: 30.03, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 4. Add to Cart + +Measure when an item is added to a shopping cart: + +```javascript +gtag("event", "add_to_cart", { + currency: "USD", + value: 30.03, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 5. Remove from Cart + +Measure when a user removes an item from cart: + +```javascript +gtag("event", "remove_from_cart", { + currency: "USD", + value: 30.03, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 6. View Cart + +When a user views the cart: + +```javascript +gtag("event", "view_cart", { + currency: "USD", + value: 30.03, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 7. Add to Wishlist + +Measure when an item is added to a wishlist: + +```javascript +gtag("event", "add_to_wishlist", { + currency: "USD", + value: 30.03, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 8. Begin Checkout + +Measure the first step in a checkout process: + +```javascript +gtag("event", "begin_checkout", { + currency: "USD", + value: 30.03, + coupon: "SUMMER_FUN", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 9. Add Shipping Info + +When a user adds shipping information: + +```javascript +gtag("event", "add_shipping_info", { + currency: "USD", + value: 30.03, + coupon: "SUMMER_FUN", + shipping_tier: "Ground", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 10. Add Payment Info + +When a user submits payment information: + +```javascript +gtag("event", "add_payment_info", { + currency: "USD", + value: 30.03, + coupon: "SUMMER_FUN", + payment_type: "Credit Card", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +### 11. Purchase + +Measure a purchase: + +```javascript +gtag("event", "purchase", { + transaction_id: "T_12345", // Required + value: 72.05, // Sum of (price * quantity) for all items + tax: 3.60, + shipping: 5.99, + currency: "USD", + coupon: "SUMMER_SALE", + customer_type: "new", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + affiliation: "Google Merchandise Store", + coupon: "SUMMER_FUN", + discount: 2.22, + index: 0, + item_brand: "Google", + item_category: "Apparel", + item_category2: "Adult", + item_category3: "Shirts", + item_category4: "Crew", + item_category5: "Short sleeve", + item_list_id: "related_products", + item_list_name: "Related Products", + item_variant: "green", + location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo", + price: 10.01, + quantity: 3 + }, + { + item_id: "SKU_12346", + item_name: "Google Grey Women's Tee", + affiliation: "Google Merchandise Store", + coupon: "SUMMER_FUN", + discount: 3.33, + index: 1, + item_brand: "Google", + item_category: "Apparel", + price: 21.01, + promotion_id: "P_12345", + promotion_name: "Summer Sale", + quantity: 2 + } + ] +}); +``` + +### 12. Refund + +Measure refunds with the relevant transaction ID: + +```javascript +gtag("event", "refund", { + currency: "USD", + transaction_id: "T_12345", // Required + value: 30.03, + coupon: "SUMMER_FUN", + shipping: 3.33, + tax: 1.11, + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + price: 10.01, + quantity: 3 + // ... other item parameters + } + ] +}); +``` + +## Promotions + +### View Promotion + +Measure promotion impressions: + +```javascript +gtag("event", "view_promotion", { + creative_name: "Summer Banner", + creative_slot: "featured_app_1", + promotion_id: "P_12345", + promotion_name: "Summer Sale", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + // ... other item parameters + } + ] +}); +``` + +### Select Promotion + +Indicate a user clicked on a promotion: + +```javascript +gtag("event", "select_promotion", { + creative_name: "Summer Banner", + creative_slot: "featured_app_1", + promotion_id: "P_12345", + promotion_name: "Summer Sale", + items: [ + { + item_id: "SKU_12345", + item_name: "Stan and Friends Tee", + // ... other item parameters + } + ] +}); +``` + +## Best Practices + +### 1. Currency Formatting + +- Always set `currency` when sending `value` (revenue) data +- Ensures revenue metrics are calculated correctly +- Use standard 3-letter ISO 4217 currency codes (e.g., "USD", "EUR", "GBP") + +### 2. Product IDs + +- Use consistent product IDs across all events +- Ensure `item_id` matches your product catalog +- Include both `item_id` and `item_name` for better reporting + +### 3. Item Array Structure + +- Populate all available ecommerce parameters you have data for +- Even if a parameter is optional, include it when you have the data +- Helps maximize the utility of your ecommerce reporting + +### 4. Event Timing + +- Fire events at the appropriate time in the user journey +- Don't fire `purchase` events until transaction is confirmed +- Fire `view_item_list` when the list initially loads, not on interaction + +### 5. Data Quality Validation + +- Use [debug mode](https://support.google.com/analytics/answer/7201382) to verify events in realtime +- Check that parameters match expected values +- Verify currency and value calculations are correct +- Test the complete funnel from browsing to purchase + +### 6. Custom Dimensions and Metrics + +- Review [custom dimension and metric limits](https://support.google.com/analytics/answer/10075209#limits) +- Plan your custom parameters within these limits +- Document your custom parameter usage + +### 7. Sample E-commerce Website + +- Use [the Google sample ecommerce website](https://enhancedecommerce.appspot.com/) as a reference +- See practical examples of proper tagging +- Test your implementation against working examples + +## Implementation Tips + +### Setting Currency at Event Level + +When setting the items array outside of the ecommerce object, set the `currency` parameter at the event level: + +```javascript +gtag("event", "purchase", { + transaction_id: "T_12345", + currency: "USD", // Event-level currency + value: 72.05, + items: [ + // items array + ] +}); +``` + +### Consistent Naming Conventions + +Maintain consistent naming across your implementation: + +- Use snake_case for event names (following GA4 conventions) +- Keep parameter names consistent with GA4 recommended events +- Document any custom parameters you add + +## Common Patterns + +### Product Listing Pages + +```javascript +// On page load +gtag("event", "view_item_list", { + item_list_id: "category_shirts", + item_list_name: "Shirts", + items: [/* all visible products */] +}); + +// On product click +gtag("event", "select_item", { + item_list_id: "category_shirts", + item_list_name: "Shirts", + items: [/* clicked product */] +}); +``` + +### Product Detail Pages + +```javascript +// On page load +gtag("event", "view_item", { + currency: "USD", + value: 29.99, + items: [/* product details */] +}); + +// On add to cart button click +gtag("event", "add_to_cart", { + currency: "USD", + value: 29.99, + items: [/* product with quantity */] +}); +``` + +### Shopping Cart + +```javascript +// When viewing cart +gtag("event", "view_cart", { + currency: "USD", + value: 150.00, + items: [/* all cart items */] +}); + +// When removing item +gtag("event", "remove_from_cart", { + currency: "USD", + value: 29.99, + items: [/* removed item */] +}); +``` + +### Checkout Flow + +```javascript +// Step 1: Begin checkout +gtag("event", "begin_checkout", { + currency: "USD", + value: 150.00, + items: [/* all cart items */] +}); + +// Step 2: Add shipping +gtag("event", "add_shipping_info", { + currency: "USD", + value: 150.00, + shipping_tier: "Ground", + items: [/* all cart items */] +}); + +// Step 3: Add payment +gtag("event", "add_payment_info", { + currency: "USD", + value: 150.00, + payment_type: "Credit Card", + items: [/* all cart items */] +}); + +// Step 4: Complete purchase +gtag("event", "purchase", { + transaction_id: "T_12345", + value: 155.99, + tax: 10.00, + shipping: 5.99, + currency: "USD", + items: [/* all purchased items */] +}); +``` + +## Resources + +- [GA4 E-commerce Documentation](https://developers.google.com/analytics/devguides/collection/ga4/ecommerce) +- [GA4 Recommended Events Reference](https://developers.google.com/analytics/devguides/collection/ga4/reference/events) +- [Set up a Purchase Event Guide](https://developers.google.com/analytics/devguides/collection/ga4/set-up-ecommerce) +- [Sample E-commerce Website](https://enhancedecommerce.appspot.com/) diff --git a/skills/gtm-datalayer/references/spa-datalayer.md b/skills/gtm-datalayer/references/spa-datalayer.md new file mode 100644 index 0000000..d6fa4b2 --- /dev/null +++ b/skills/gtm-datalayer/references/spa-datalayer.md @@ -0,0 +1,472 @@ +# Data Layer for Single-Page Applications (SPAs) + +**Source**: https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications +**Last Updated**: 2025-01-09 + +## Overview + +Single-Page Applications (SPAs) are websites that load an HTML document once and fetch additional content using JavaScript APIs. Unlike traditional multi-page applications, SPAs require special handling for tracking page views and user interactions because the browser doesn't reload the page when users navigate between different screens. + +The key challenge with SPAs is that traditional page view tracking relies on full page loads, but in SPAs, navigation happens dynamically without triggering a new page load event. + +## Why SPAs Require Special Treatment + +### Traditional vs SPA Page Loads + +**Traditional Website:** +- User clicks a link +- Browser requests a new HTML document +- Page fully reloads +- Analytics tags fire automatically on load + +**Single-Page Application:** +- User clicks a link +- JavaScript updates the DOM +- URL may change (via History API) +- No page reload occurs +- Analytics tags don't automatically fire + +### Key Measurement Goals + +To accurately track SPAs, you need to: + +1. **Count page views for each screen** a user interacts with +2. **Track the page referrer correctly** to trace the user journey +3. **Maintain proper event sequencing** as users navigate +4. **Clear previous page data** to avoid data carryover +5. **Update page-specific parameters** for each virtual page view + +## Implementation Methods + +### Method 1: Browser History Changes (Recommended) + +Use this method if your SPA uses the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History), specifically the `pushState()` and `replaceState()` methods to update screens. + +#### How It Works + +GTM's **History Change trigger** listens for: +- Changes to the URL fragment (hash) +- Calls to `history.pushState()` +- Calls to `history.replaceState()` +- Browser back/forward button clicks (`popstate` event) + +#### GTM Setup + +1. **Enable Built-in History Variables** in GTM: + - History Old URL Fragment + - History New URL Fragment + - History Old State + - History New State + - History Source (pushState, replaceState, popstate, or hashchange) + +2. **Create a History Change Trigger:** + - Go to Triggers > New + - Choose "History Change" as trigger type + - Configure any additional filters if needed + - This trigger fires whenever the URL changes without a page reload + +3. **Create a Virtual Page View Tag:** + - Create a GA4 Event tag + - Event Name: `page_view` + - Set to fire on the History Change trigger + - Configure page parameters (page_location, page_title, etc.) + +#### Example Implementation + +```javascript +// Your SPA framework (e.g., React Router) handles navigation +// It uses pushState internally: +history.pushState({ page: 'products' }, 'Products', '/products'); + +// GTM's History Change trigger automatically detects this +// and fires your configured tags +``` + +#### Data Layer Push Pattern + +When using History Change triggers, push updated page data to the data layer: + +```javascript +// Clear previous page data and push new page data +window.dataLayer.push({ + event: 'virtual_pageview', + page_path: '/new-page', + page_title: 'New Page Title', + page_location: window.location.href, + // Clear previous page-scoped variables + previous_page_data: undefined, + transaction_id: undefined // Clear transaction-specific data +}); +``` + +### Method 2: Custom Events + +Use this method if your website uses the [`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) object to render different screens, or if you need more control over when virtual page views fire. + +#### How It Works + +You manually push custom events to the data layer when screen changes occur, rather than relying on automatic History API detection. + +#### Implementation + +```javascript +// In your SPA's route change handler +function onRouteChange(newRoute) { + // Update the DOM with new content + updateContent(newRoute); + + // Push virtual pageview to data layer + window.dataLayer.push({ + event: 'virtual_pageview', + page_path: newRoute.path, + page_title: newRoute.title, + page_location: window.location.href + }); +} +``` + +#### GTM Setup + +1. **Create a Custom Event Trigger:** + - Go to Triggers > New + - Choose "Custom Event" as trigger type + - Event name: `virtual_pageview` + +2. **Create Variables for Page Data:** + - Data Layer Variable for `page_path` + - Data Layer Variable for `page_title` + - Data Layer Variable for `page_location` + +3. **Create a GA4 Event Tag:** + - Event Name: `page_view` + - Add event parameters using the variables created above + - Set to fire on the Custom Event trigger + +## Data Layer Clearing Best Practices + +### Why Clear Data + +In SPAs, data persists in the data layer until explicitly cleared. This can cause: +- Transaction data appearing on non-transaction pages +- User data from one session bleeding into another +- Incorrect attribution of events to pages + +### What to Clear + +**Page-Scoped Data** (clear on every virtual pageview): +- `page_title` +- `page_path` +- `page_category` +- Custom page dimensions + +**Event-Scoped Data** (clear after the event fires): +- `transaction_id` +- `ecommerce` objects +- Form submission data +- Click data + +**Persistent Data** (keep across page views): +- `user_id` +- `user_type` +- Session-level dimensions + +### Clearing Pattern + +```javascript +// Pattern 1: Clear and set in separate pushes +dataLayer.push(function() { + this.reset(); // Clear all data layer state +}); + +dataLayer.push({ + event: 'virtual_pageview', + page_title: 'New Page', + page_path: '/new-page' +}); + +// Pattern 2: Clear and set in one push (set to undefined) +dataLayer.push({ + event: 'virtual_pageview', + // Clear previous values + transaction_id: undefined, + ecommerce: undefined, + form_id: undefined, + // Set new values + page_title: 'New Page', + page_path: '/new-page' +}); +``` + +## Framework-Specific Implementation + +### React with React Router + +```javascript +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +function Analytics() { + const location = useLocation(); + + useEffect(() => { + // Clear previous page data + window.dataLayer.push({ + event: 'virtual_pageview', + page_path: location.pathname, + page_title: document.title, + page_location: window.location.href, + // Clear event-scoped data + transaction_id: undefined, + ecommerce: undefined + }); + }, [location]); + + return null; +} + +export default Analytics; +``` + +### Vue.js with Vue Router + +```javascript +// In your main.js or router configuration +import router from './router'; + +router.afterEach((to, from) => { + window.dataLayer.push({ + event: 'virtual_pageview', + page_path: to.path, + page_title: to.meta.title || document.title, + page_location: window.location.href, + // Clear event-scoped data + transaction_id: undefined, + ecommerce: undefined + }); +}); +``` + +### Angular with Angular Router + +```typescript +import { Router, NavigationEnd } from '@angular/router'; +import { filter } from 'rxjs/operators'; + +export class AppComponent { + constructor(private router: Router) { + this.router.events.pipe( + filter(event => event instanceof NavigationEnd) + ).subscribe((event: NavigationEnd) => { + window.dataLayer.push({ + event: 'virtual_pageview', + page_path: event.urlAfterRedirects, + page_title: document.title, + page_location: window.location.href, + // Clear event-scoped data + transaction_id: undefined, + ecommerce: undefined + }); + }); + } +} +``` + +### Next.js + +```javascript +// In pages/_app.js +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; + +function MyApp({ Component, pageProps }) { + const router = useRouter(); + + useEffect(() => { + const handleRouteChange = (url) => { + window.dataLayer.push({ + event: 'virtual_pageview', + page_path: url, + page_title: document.title, + page_location: window.location.href, + // Clear event-scoped data + transaction_id: undefined, + ecommerce: undefined + }); + }; + + router.events.on('routeChangeComplete', handleRouteChange); + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router.events]); + + return ; +} + +export default MyApp; +``` + +## Testing and Debugging SPAs + +### Preview Mode in GTM + +1. **Enable Preview Mode** in your GTM container +2. Navigate through your SPA +3. Watch for: + - History Change events firing + - Custom Event triggers activating + - Data layer state changes + - Tag firing sequence + +### Debug Console Checklist + +For each virtual page view, verify: + +- [ ] `page_view` event fires +- [ ] `page_location` updates correctly +- [ ] `page_title` reflects the new screen +- [ ] Previous page's `page_location` becomes current `page_referrer` +- [ ] Event-scoped data is cleared +- [ ] User/session data persists + +### DebugView in GA4 + +1. **Enable Debug Mode** in GTM tags: + ```javascript + // Add to your data layer push + window.dataLayer.push({ + event: 'virtual_pageview', + debug_mode: true, + // ... other parameters + }); + ``` + +2. **Navigate through your SPA** + +3. **Check in GA4 DebugView:** + - Each screen change should create a new `page_view` event + - Event parameters should update correctly + - Event sequence should match user journey + +### Common SPA Pitfalls + +#### 1. Data Carryover + +**Problem:** Transaction data appears on non-checkout pages + +**Solution:** +```javascript +// Always clear ecommerce data after it's sent +dataLayer.push({ + event: 'purchase', + ecommerce: { /* purchase data */ } +}); + +// On next page view, clear it +dataLayer.push({ + event: 'virtual_pageview', + ecommerce: undefined, // Explicitly clear + transaction_id: undefined +}); +``` + +#### 2. Missing Virtual Pageviews + +**Problem:** Some navigation doesn't trigger page views + +**Solution:** Ensure all route changes push to data layer, including: +- Back/forward button navigation +- Hash changes +- Modal/dialog state changes (if tracking as pages) + +#### 3. Duplicate Page Views + +**Problem:** Both real and virtual page views fire + +**Solution:** +```javascript +// Block the initial page_view from GA4 Config tag +// Use a Pageview trigger with "Fire on DOM Ready" exception +// Or configure GA4 tag to not send page_view on initial load +``` + +#### 4. Incorrect Referrer + +**Problem:** `page_referrer` doesn't reflect previous virtual page + +**Solution:** GTM automatically handles this when using History Change triggers. For custom events, ensure you're not manually setting `page_referrer`. + +## Advanced Patterns + +### Conditional Virtual Pageviews + +Only track certain route changes: + +```javascript +function shouldTrackPageview(newRoute) { + // Don't track hash-only changes + if (newRoute.startsWith('#')) return false; + + // Don't track query parameter-only changes + if (isSamePath(currentRoute, newRoute)) return false; + + return true; +} + +if (shouldTrackPageview(newRoute)) { + dataLayer.push({ + event: 'virtual_pageview', + page_path: newRoute + }); +} +``` + +### Enhanced Virtual Pageview Data + +Include additional context with virtual pageviews: + +```javascript +dataLayer.push({ + event: 'virtual_pageview', + page_path: '/products/category/shoes', + page_title: 'Shoes - Products', + page_location: window.location.href, + // Additional context + page_category: 'Products', + page_subcategory: 'Shoes', + content_group: 'Product Listing', + user_journey_step: 'Browse', + spa_route_type: 'category_page' +}); +``` + +## Verification Checklist + +Before deploying your SPA tracking: + +- [ ] History Change trigger configured (if using History API) +- [ ] Custom Event trigger configured (if using custom events) +- [ ] Data Layer Variables created for page parameters +- [ ] GA4 Event tag configured with `page_view` event +- [ ] Initial page load tracked correctly +- [ ] All navigation types trigger virtual pageviews: + - [ ] Forward navigation + - [ ] Back button + - [ ] Direct URL changes + - [ ] Hash changes (if applicable) +- [ ] Data clearing implemented for: + - [ ] Transaction data + - [ ] Event-scoped variables + - [ ] Page-scoped variables +- [ ] Tested in Preview Mode +- [ ] Verified in GA4 DebugView +- [ ] Event sequence matches user journey +- [ ] Page referrer updates correctly + +## Resources + +- [Google Analytics: Measure Single-Page Applications](https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications) +- [GTM History Change Trigger](https://support.google.com/tagmanager/answer/7679322) +- [GTM Built-in Variables for Web](https://support.google.com/tagmanager/answer/7182738) +- [History API Documentation (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/History) +- [Single-Page Applications (MDN)](https://developer.mozilla.org/en-US/docs/Glossary/SPA) diff --git a/skills/gtm-debugging/SKILL.md b/skills/gtm-debugging/SKILL.md new file mode 100644 index 0000000..cb17ae1 --- /dev/null +++ b/skills/gtm-debugging/SKILL.md @@ -0,0 +1,113 @@ +--- +name: gtm-debugging +description: Expert guidance for debugging and testing Google Tag Manager implementations using Preview mode, Tag Assistant, debug console, and troubleshooting common issues like tags not firing, undefined variables, or timing problems. Use when debugging GTM, troubleshooting why tags aren't firing, testing GTM configurations, or analyzing GTM issues. +--- + +# GTM Debugging & Testing + +## Overview +This skill provides expertise for debugging and testing Google Tag Manager implementations using Preview mode, Tag Assistant, and systematic troubleshooting approaches. + +## When to Use This Skill +Invoke this skill when: +- Debugging why tags aren't firing +- Testing GTM implementations +- Troubleshooting undefined variables +- Investigating timing or sequencing issues +- Using GTM Preview mode +- Working with Tag Assistant +- Validating GTM configurations +- Comparing GTM versions + +## Preview Mode & Debug Console + +### Enable Preview Mode +1. Open GTM container +2. Click "Preview" button +3. Enter your website URL +4. Debug window opens + +### Debug Console Sections +- **Summary** - All events fired on page +- **Tags** - Tags fired and not fired +- **Variables** - All variable values +- **Data Layer** - Data layer state at each event +- **Errors** - JavaScript errors and issues + +## Common Debugging Workflows + +### Debug Tag Not Firing +1. Enter Preview Mode +2. Trigger the event +3. Check "Tags Not Fired" section +4. Review trigger conditions in Variables tab +5. Inspect Data Layer +6. Fix conditions and retest + +### Debug Undefined Variable +1. Find the event where variable is needed +2. Check Variables tab for the variable +3. Verify data layer contains the value +4. Check variable configuration (path, type) +5. Verify timing (is data available when tag fires?) + +### Debug Data Layer Issues +1. Open Data Layer tab in debug console +2. Find the event where data should be pushed +3. Verify data structure matches expectations +4. Check for typos in variable names +5. Validate data types (string, number, object) + +## Common Issues & Solutions + +### Tag Fires on Wrong Pages +- Check trigger conditions +- Verify Page URL or Page Path filters +- Review trigger exceptions +- Test with different URL patterns + +### Variable Returns Undefined +- Check data layer path (dot notation) +- Verify timing (pushed before tag fires?) +- Check for typos in variable names +- Validate data layer structure + +### Tag Fires Multiple Times +- Check for duplicate triggers +- Review History Change triggers on SPAs +- Verify event deduplication +- Check tag sequencing + +### Performance Issues +- Review number of tags firing +- Check custom HTML execution time +- Audit unnecessary triggers +- See gtm-best-practices skill for optimization + +## Tag Assistant +- Chrome extension for GTM debugging +- Real-time tag validation +- HTTP request inspection +- GA4 event validation + +## Testing Checklist +- [ ] Tags fire on correct pages/events +- [ ] Variables populate with correct values +- [ ] No JavaScript errors in console +- [ ] Data layer pushes correctly +- [ ] Triggers activate as expected +- [ ] No duplicate tag fires +- [ ] Performance is acceptable + +## References +- **references/debugging-testing.md** - Debug console, preview mode, testing workflows + +## Integration with Other Skills +## Integration with Other Skills +- **gtm-general** - General GTM guidance and concepts +- **gtm-tags** - Understand tag configuration +- **gtm-triggers** - Understand trigger setup +- **gtm-variables** - Understand variable configuration +- **gtm-datalayer** - Debug data layer implementation +- **gtm-setup** - Verify container installation +- **gtm-best-practices** - Testing and optimization best practices diff --git a/skills/gtm-debugging/references/debugging-testing.md b/skills/gtm-debugging/references/debugging-testing.md new file mode 100644 index 0000000..86c362d --- /dev/null +++ b/skills/gtm-debugging/references/debugging-testing.md @@ -0,0 +1,642 @@ +# Google Tag Manager - Debugging and Testing + +**Sources**: +- https://support.google.com/tagmanager/answer/6107056 +- https://developers.google.com/tag-platform/tag-manager/server-side/debug + +**Last Updated**: 2025-01-09 + +## Overview + +Testing and debugging are critical components of any Google Tag Manager implementation. GTM provides comprehensive tools to help you verify that tags fire correctly, triggers activate as expected, and data flows accurately to your analytics platforms. + +## Preview Mode + +Preview mode (formerly called Debug mode) allows you to test your GTM container configuration before publishing it to production. When enabled, you can browse your website with the unpublished container draft and see exactly how tags behave. + +### Enabling Preview Mode + +1. **Navigate to GTM** at https://tagmanager.google.com +2. **Open your container** workspace +3. **Click "Preview"** in the top right corner +4. **Tag Assistant launches** in a new tab +5. **Enter your website URL** +6. **Optional**: Uncheck "Include debug signal in the URL" if it breaks your site +7. **Click "Connect"** +8. **Your website opens** in a new window with "Connected" indicator +9. **Return to Tag Assistant** and click "Continue" + +The debug interface appears, showing detailed information about tag firing, triggers, variables, and the data layer. + +### Tag Assistant Companion + +Installing the [Tag Assistant Companion Chrome extension](https://chrome.google.com/webstore/detail/tag-assistant-companion/jmekfmbnaedfebfnmakmokmlfpblbfdm) improves the preview experience by opening your website in a new tab instead of a popup window. + +### Previewing Older Versions + +To test a previously published version: + +1. Go to **Versions** in your workspace navigation +2. Find the version to preview +3. Click **More Actions** (three dots) +4. Select **Preview** + +## Using the Debug Interface + +### Interface Overview + +The debug interface consists of several key areas: + +- **Summary Panel**: Overview of the current event and tag firing status +- **Tags Panel**: Lists which tags fired and which didn't +- **Data Layer Panel**: Shows the data layer state and events +- **Variables Panel**: Displays variable values for the current event +- **Errors Panel**: Lists JavaScript errors and tag failures + +### Summary View + +Shows a timeline of events as you interact with your website: + +``` +Container Loaded + Page View + DOM Ready + Window Loaded + Click (Button) + Custom Event (form_submit) +``` + +Click any event to see: +- Which tags fired +- Which triggers activated +- Current data layer state +- Variable values at that moment + +### Tags View + +**Tags Fired:** +- Green indicators show successfully fired tags +- Red indicators show failed tags +- Number shows how many times the tag fired +- Click a tag to see: + - Tag configuration + - Triggering conditions + - Blocking triggers + - Execution order + +**Tags Not Fired:** +- Shows tags that didn't fire for this event +- Click to see why (trigger conditions not met, blocking triggers, etc.) + +### Data Layer View + +Displays the data layer's state: + +```javascript +{ + "gtm.start": 1704844800000, + "event": "gtm.js", + "gtm.uniqueEventId": 1 +} +``` + +Shows: +- All pushes to the data layer +- Current state of the data layer +- Event sequence +- Custom data layer variables + +### Variables View + +Lists all variables and their current values: + +| Variable Name | Value | Type | +|--------------|-------|------| +| Page URL | https://example.com/products | Built-in | +| Page Path | /products | Built-in | +| Product Name | Blue Widget | Data Layer Variable | +| User ID | 12345 | Data Layer Variable | + +Helps verify: +- Variables resolve to expected values +- Data layer variables capture correct data +- Custom JavaScript variables execute properly + +### Errors View + +Shows JavaScript errors and tag execution failures: + +``` +Tag: GA4 Event - Purchase +Error: sendBeacon failed - network error + +Tag: Facebook Pixel +Error: fbq is not defined +``` + +Critical for identifying: +- Missing vendor libraries +- JavaScript syntax errors +- Network failures +- Configuration issues + +## Sharing Preview Mode + +### Share with Colleagues + +To let others view your debug session: + +1. **In Tag Assistant**, click **More Actions** (three dots) +2. **Select "Share"** +3. **Enter your website domain** +4. **Copy the preview URL** +5. **Send to colleagues** + +The recipient can: +- Connect to your site in preview mode +- View the Tag Assistant debug interface +- See the same container draft you're testing + +**Note**: The preview URL is temporary and expires after the debug session ends. + +### Share Specific Versions + +From the Versions tab: + +1. Click **More Actions** on any version +2. Select **Share Preview** +3. Enter target website domain +4. Copy and share the preview URL + +## Debugging Common Scenarios + +### Tag Not Firing + +**Checklist:** + +1. **Check the trigger** + - Is the trigger configured correctly? + - Are the trigger conditions being met? + - Check trigger variables resolve correctly + +2. **Check for blocking triggers** + - Does the tag have any exception/blocking triggers? + - Are those triggers inadvertently activating? + +3. **Check tag priority/sequencing** + - Does another tag need to fire first? + - Is tag sequencing configured correctly? + +4. **Check the data layer** + - Is required data present when trigger evaluates? + - Are data layer variables returning expected values? + +### Trigger Not Activating + +**Debugging Steps:** + +1. **Verify trigger conditions** + ``` + Example: Click trigger on CSS selector + - Check selector syntax + - Verify element exists on page + - Test with simpler selector + ``` + +2. **Check built-in variables** + - Are required built-in variables enabled? + - Click Variables > Configure + - Enable missing variables + +3. **Test variable values** + - Check Variables panel + - Verify trigger conditions match actual values + - Look for type mismatches (string vs number) + +### Variable Returning Undefined + +**Common Causes:** + +1. **Data layer variable not found** + ```javascript + // Data layer: + { "product_name": "Widget" } + + // Variable configured as: + productName // Wrong - case sensitive! + + // Should be: + product_name // Correct + ``` + +2. **Timing issue** + - Data pushed after tag fires + - Use Custom Event trigger to wait for data + +3. **Incorrect data layer version** + - Check Data Layer Version setting + - Version 2 vs Version 1 syntax differs + +### Data Layer Issues + +**Debug Pattern:** + +```javascript +// In browser console, inspect data layer +console.log(window.dataLayer); + +// Check for specific event +window.dataLayer.filter(item => item.event === 'purchase'); + +// Watch for new pushes +var originalPush = window.dataLayer.push; +window.dataLayer.push = function() { + console.log('DataLayer push:', arguments[0]); + return originalPush.apply(this, arguments); +}; +``` + +### JavaScript Errors + +**Investigation Steps:** + +1. **Check browser console** (F12 > Console) +2. **Look for errors** before tag execution +3. **Verify vendor scripts** load correctly +4. **Check for conflicts** between tags +5. **Test with tags disabled** one at a time + +## Server-Side Debugging + +For server-side GTM containers, the debug interface differs: + +### Server Preview Layout + +**Left Panel**: Incoming HTTP requests +``` +collect?v=2&tid=G-XXXXXX&en=page_view... + └─ page_view + └─ scroll + └─ add_to_cart +``` + +**Right Panel Tabs**: +- Request +- Tags +- Variables +- Event Data +- Console + +### Request Tab + +**Client Box**: Shows which client claimed the request + +**Incoming HTTP Request**: Full request details +- Headers +- Query parameters +- Request body +- Should match what's in Chrome Network tab + +**Outgoing HTTP Requests**: Requests sent to vendors +- Tag that generated the request +- Request details (URL, headers, body) +- HTTP response from vendor +- Status code and response body + +### Debugging Server Tags + +**Tags Fired**: +- Success/failure status +- Number of times fired +- Click to see: + - Tag properties + - Outgoing HTTP requests + - Firing triggers + +**Tags Not Fired**: +- Available tags that didn't fire +- Reason they didn't fire + +### Server Variables + +Shows: +- Variable type +- Return type +- Current value + +Example: +``` +var_screen_resolution: "1536x864" +var_user_agent: "Mozilla/5.0..." +var_client_id: "123456.789012" +``` + +### Event Data Tab + +Full event data object: +```json +{ + "event_name": "purchase", + "value": 129.99, + "currency": "USD", + "transaction_id": "T_12345", + "items": [...] +} +``` + +This data populates variable values. + +### Server Console + +Shows errors from fired tags: +``` +Tag: GA4 Server Tag +Error: Invalid endpoint URL + +Tag: Facebook Conversions API +Error: HTTP 401 - Invalid access token +``` + +## Testing Workflows + +### Pre-Publishing Checklist + +Before publishing any container version: + +- [ ] Enable preview mode +- [ ] Test on all key pages: + - [ ] Homepage + - [ ] Product pages + - [ ] Category pages + - [ ] Cart/checkout + - [ ] Confirmation page +- [ ] Verify all critical tags fire +- [ ] Check data layer on each page +- [ ] Test all conversion events +- [ ] Verify cross-domain tracking +- [ ] Test with ad blockers disabled and enabled +- [ ] Check mobile experience (responsive design mode) +- [ ] Verify no JavaScript errors +- [ ] Confirm data in vendor platforms (GA4, Ads, etc.) + +### Testing Different Environments + +Use GTM Environments for staged testing: + +1. **Development Environment** + - Test unstable/experimental changes + - Break things without consequence + - Rapid iteration + +2. **Staging Environment** + - Test on staging server + - QA team validation + - Stakeholder review + +3. **Production Environment** + - Live site + - Real user data + - Careful change management + +### Cross-Domain Testing + +When testing cross-domain tracking: + +1. **Enable preview** on primary domain +2. **Navigate to secondary** domain +3. **Verify preview mode** persists +4. **Check data layer** for linker parameters +5. **Confirm client IDs** match across domains + +### Version Comparison + +Compare container versions: + +1. Go to **Versions** +2. Select two versions +3. Click **Compare** +4. Review differences: + - Tags added/removed/modified + - Trigger changes + - Variable updates + +## Browser Developer Tools + +### Network Tab + +Monitor tag requests: + +1. **Open DevTools** (F12) +2. **Go to Network tab** +3. **Filter**: "analytics", "gtm", "google-analytics" +4. **Trigger events** on your site +5. **Inspect requests**: + - Check parameters sent + - Verify timing + - Look for errors (red status codes) + +### Console Tab + +Monitor JavaScript execution: + +```javascript +// Check GTM loaded +console.log(google_tag_manager); + +// Check dataLayer +console.log(dataLayer); + +// Monitor events +dataLayer.push = new Proxy(dataLayer.push, { + apply: function(target, thisArg, args) { + console.log('DataLayer event:', args[0]); + return target.apply(thisArg, args); + } +}); +``` + +### Application Tab + +Inspect cookies: + +1. **Open Application tab** +2. **Go to Cookies** +3. **Select your domain** +4. **Check for**: + - `_ga` (GA client ID) + - `_gid` (GA session ID) + - Custom cookies + +## GA4 DebugView + +For GA4-specific debugging: + +1. **Enable debug mode** in GTM: + ```javascript + // Add to data layer + window.dataLayer.push({ + 'debug_mode': true + }); + ``` + +2. **Or use Chrome extension**: Google Analytics Debugger + +3. **Open GA4 DebugView**: + - Go to GA4 property + - Navigate to Configure > DebugView + - See real-time events from your debug session + +4. **Verify in DebugView**: + - Events appear immediately + - Parameters are correct + - Event sequence matches expectations + +## Common Debugging Patterns + +### Test Ecommerce Flow + +```javascript +// 1. View item list +dataLayer.push({ + event: 'view_item_list', + ecommerce: { items: [...] } +}); + +// 2. Add to cart +dataLayer.push({ + event: 'add_to_cart', + ecommerce: { items: [...] } +}); + +// 3. Purchase +dataLayer.push({ + event: 'purchase', + ecommerce: { + transaction_id: 'T_12345', + value: 129.99, + items: [...] + } +}); + +// Verify each step in Preview Mode +``` + +### Test Conditional Logic + +```javascript +// Test trigger with conditions +if ({{Page Path}} contains '/products/') { + // Should fire: Product View tag + // Should not fire: Homepage tag +} + +// Verify in Variables panel +``` + +### Test Tag Sequencing + +```javascript +// Tag A must fire before Tag B +// Tag A: Setup Tag +// - No sequencing + +// Tag B: Cleanup Tag +// - Setup Tag: Tag A +// - Firing: After Tag A + +// Verify firing order in Summary panel +``` + +## Troubleshooting Tips + +### Clear Browser Cache + +Sometimes old container versions cache: +``` +1. Hard refresh: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac) +2. Clear cache completely +3. Close and reopen preview mode +``` + +### Check Container Version + +Verify correct container loads: +```javascript +// In console: +google_tag_manager['GTM-XXXXX'].dataLayer.get('gtm.version') +``` + +### Verify Container ID + +Ensure correct container on page: +```html + + +``` + +### Multiple Containers + +If multiple containers exist: +- Each fires independently +- Check all containers in preview +- Avoid duplicate tags across containers + +## Best Practices + +### Testing Best Practices + +1. **Test before publishing** - Always use preview mode +2. **Test on real pages** - Not just homepage +3. **Test user journeys** - Complete flows, not just individual pages +4. **Document your tests** - Use version notes +5. **Get peer review** - Have colleagues review in preview mode +6. **Test with real data** - Use production-like scenarios + +### Debugging Best Practices + +1. **Start simple** - Test one thing at a time +2. **Check the basics first** - Is GTM loading? Is the tag enabled? +3. **Use the data layer** - Don't rely on page scraping +4. **Log everything** - Use version notes to document changes +5. **Keep containers clean** - Remove unused tags/triggers/variables + +### Documentation + +When testing, document: + +``` +Version: 123 +Changes: +- Added GA4 purchase event +- Modified product click trigger +- Updated checkout flow + +Tests Performed: +- ✅ Purchase event fires on confirmation page +- ✅ Product clicks tracked correctly +- ✅ Checkout funnel complete +- ❌ Cross-domain tracking issue on subdomain (to fix) + +Tested By: Henrik +Date: 2025-01-09 +``` + +## Exit Preview Mode + +To stop debugging: + +1. **Click X** in Tag Assistant debug interface +2. **Click "Stop debugging"** on Tag Assistant page +3. **Close preview window** + +Preview mode only affects your browser - regular visitors don't see the debug interface. + +## Resources + +- [GTM Preview and Debug Containers](https://support.google.com/tagmanager/answer/6107056) +- [Preview and Debug Server Containers](https://developers.google.com/tag-platform/tag-manager/server-side/debug) +- [Tag Assistant](https://support.google.com/tagassistant/answer/10039345) +- [GA4 DebugView](https://support.google.com/analytics/answer/7201382) +- [GTM Troubleshooting](https://support.google.com/tagmanager/topic/9002003) diff --git a/skills/gtm-general/SKILL.md b/skills/gtm-general/SKILL.md new file mode 100644 index 0000000..9e6ce24 --- /dev/null +++ b/skills/gtm-general/SKILL.md @@ -0,0 +1,334 @@ +--- +name: gtm-general +description: General guidance and overview for Google Tag Manager (GTM), including what GTM is, how it works, GTM architecture, account structure, workspace concepts, version control, publishing workflows, when to use GTM, GTM vs hardcoded tags, container types (web, mobile, server-side), GTM terminology, common use cases, and routing to specialized GTM skills. Use when asking general questions about GTM, learning about GTM fundamentals, understanding GTM concepts, planning GTM implementation strategy, comparing GTM approaches, getting started with GTM, or when unsure which specific GTM skill to use. +--- + +# Google Tag Manager - General Guidance + +## Overview +General guidance for Google Tag Manager (GTM) covering fundamentals, concepts, architecture, and routing to specialized skills for specific GTM tasks. + +## When to Use This Skill +Invoke this skill when: +- Asking general questions about Google Tag Manager +- Learning GTM fundamentals and core concepts +- Understanding how GTM works and its architecture +- Planning GTM implementation strategy +- Deciding whether to use GTM vs other approaches +- Understanding GTM terminology and concepts +- Getting oriented with GTM capabilities +- Unsure which specific GTM skill applies to your question +- Comparing GTM container types or approaches +- Understanding GTM account structure and organization + +## What is Google Tag Manager? + +Google Tag Manager (GTM) is a free tag management system that allows you to deploy and manage marketing tags (tracking codes, analytics snippets, pixels) on your website or mobile app without modifying code directly. + +**Key Benefits:** +- **No code deployments** - Add/modify tags without developer assistance +- **Faster implementation** - Deploy tags in minutes instead of weeks +- **Version control** - Track changes, test, and rollback if needed +- **Centralized management** - Manage all tags in one place +- **Built-in templates** - Pre-built tags for GA4, Google Ads, Facebook, etc. +- **Preview & debug** - Test before publishing to production +- **Performance** - Async loading, conditional firing + +## GTM Architecture & Concepts + +### Container Structure +**Account** (Organization level) +└── **Container** (Website/App level) + ├── **Workspace** (Development environment) + │ ├── Tags (What to fire) + │ ├── Triggers (When to fire) + │ └── Variables (Data to capture) + └── **Versions** (Snapshots of container state) + +### Core Components + +**Tags** - Code snippets to fire (GA4, Google Ads, pixels, custom HTML) +- See **gtm-tags** skill for tag configuration + +**Triggers** - Rules for when tags should fire (page views, clicks, events) +- See **gtm-triggers** skill for trigger configuration + +**Variables** - Dynamic values used in tags and triggers (URLs, data layer values, cookies) +- See **gtm-variables** skill for variable configuration + +**Data Layer** - JavaScript object for passing data to GTM +- See **gtm-datalayer** skill for data layer implementation + +### Container Types + +**Web Container** - For websites +- Most common container type +- Uses GTM snippet in HTML +- Supports page view, click, form, scroll tracking +- See **gtm-setup** skill for installation + +**Mobile Container** - For iOS/Android apps +- Integrates with Firebase +- Different trigger types (screen view, app events) + +**Server-Side Container** - For server-side tagging +- Runs on server infrastructure (Google Cloud, Cloudflare Workers, etc.) +- First-party data collection +- Enhanced privacy and control +- Better performance (offload client-side tags) +- See **gtm-api** skill for programmatic management + +### Workspace & Version Control + +**Workspaces** - Isolated development environments +- Multiple team members can work simultaneously +- Changes don't affect live container until published +- Merge changes when ready + +**Versions** - Snapshots of container configuration +- Create version when ready to publish +- Version history maintained +- Rollback to previous version if needed +- Document changes in version notes + +**Environments** - Different deployment targets +- **Live/Production** - Published container serving real traffic +- **Latest** - Most recent version (for testing) +- **Custom environments** - Development, staging, QA + +## Common Use Cases + +### Analytics Tracking +- Google Analytics 4 (GA4) implementation +- Page view tracking +- Event tracking (clicks, form submits, downloads) +- E-commerce tracking +- User behavior analysis +- See **gtm-tags** and **gtm-datalayer** skills + +### Marketing & Advertising +- Google Ads conversion tracking +- Google Ads remarketing +- Facebook Pixel / Meta Pixel +- LinkedIn Insight Tag +- TikTok Pixel, Twitter/X Pixel +- See **gtm-tags** skill + +### Custom Implementations +- A/B testing tools (Optimizely, VWO, Google Optimize) +- Chat widgets (Intercom, Drift) +- Heatmap tools (Hotjar, Crazy Egg) +- Custom tracking scripts +- See **gtm-tags** skill for custom HTML + +### Consent Management +- Cookie consent implementation +- Consent Mode v2 (Google) +- Tag blocking based on consent +- See **gtm-best-practices** skill + +## GTM vs Other Approaches + +### GTM vs Hardcoded Tags +**Hardcoded Tags** (Traditional Approach): +- Tags embedded directly in HTML/JavaScript +- Requires developer for every change +- No version control +- Difficult to debug +- Slow to implement changes + +**GTM** (Tag Management): +- Tags managed in GTM interface +- No code changes for tag updates +- Built-in version control and rollback +- Preview & debug mode +- Fast implementation (minutes vs weeks) + +**When to use hardcoded**: Very simple sites, critical tags that must never change, single tag with no management needs + +**When to use GTM**: Most websites, multiple tags, frequent tag changes, marketing/analytics tags + +### GTM vs Google Analytics 4 Direct +- GTM provides flexibility to add/modify GA4 without code changes +- GTM allows multiple marketing tags beyond just GA4 +- GA4 can be implemented without GTM (direct script), but less flexible +- Recommendation: Use GTM for GA4 unless extremely simple use case + +## Getting Started with GTM + +### 1. Create GTM Account & Container +- Sign up at tagmanager.google.com +- Create account (organization level) +- Create container (website/app level) +- See **gtm-setup** skill for detailed setup + +### 2. Install GTM Container +- Copy GTM snippet +- Add to `` and `` of all pages +- Verify installation with Preview mode +- See **gtm-setup** skill for installation + +### 3. Set Up Basic Tags +- Enable built-in variables +- Create GA4 configuration tag +- Create page view triggers +- Test in Preview mode +- See **gtm-tags** and **gtm-triggers** skills + +### 4. Implement Data Layer (if needed) +- Plan data layer structure +- Implement dataLayer.push() calls +- Create data layer variables +- See **gtm-datalayer** skill + +### 5. Test & Publish +- Use Preview mode to test +- Verify tags fire correctly +- Check data in analytics platforms +- Create version and publish +- See **gtm-debugging** skill + +## GTM Terminology + +**Auto-Event Variables** - Built-in variables for click, form, scroll events +**Built-in Variables** - Pre-configured variables (Page URL, Referrer, etc.) +**Community Template Gallery** - Shared tag/variable templates +**Container ID** - Unique ID for your GTM container (GTM-XXXXXX) +**Debug Mode** - GTM Preview mode for testing +**Firing Trigger** - Trigger that causes tag to fire +**Lookup Table** - Variable that maps inputs to outputs +**Preview Mode** - Testing mode to debug GTM before publishing +**Tag Sequencing** - Control order of tag firing +**User-Defined Variable** - Custom variable you create +**Workspace** - Development environment for making changes + +## Routing to Specialized Skills + +Based on your specific GTM need, use these specialized skills: + +**Container Setup & Installation** +→ Use **gtm-setup** skill +- Creating containers +- Installing GTM snippet +- Workspace management +- Environment configuration + +**Tag Configuration** +→ Use **gtm-tags** skill +- Creating GA4, Google Ads, marketing tags +- Custom HTML tags +- Tag settings and firing priority + +**Trigger Setup** +→ Use **gtm-triggers** skill +- Page view, click, form triggers +- Custom event triggers +- Trigger conditions and RegEx + +**Variable Configuration** +→ Use **gtm-variables** skill +- Data layer variables +- Custom JavaScript variables +- Built-in variables + +**Data Layer Implementation** +→ Use **gtm-datalayer** skill +- Data layer structure +- E-commerce tracking +- Custom events +- SPA patterns + +**Debugging & Testing** +→ Use **gtm-debugging** skill +- Preview mode +- Tag Assistant +- Troubleshooting tags not firing + +**Best Practices & Optimization** +→ Use **gtm-best-practices** skill +- Naming conventions +- Performance optimization +- Security practices +- Privacy compliance + +**Custom Template Development** +→ Use **gtm-custom-templates** skill +- Building custom tag templates +- Sandboxed JavaScript +- Template publishing + +**API & Automation** +→ Use **gtm-api** skill +- GTM API v2 +- Programmatic container management +- Automation scripts + +## Common Questions + +**Q: Do I need GTM if I only use Google Analytics?** +A: GTM is recommended even for GA-only setups because it provides flexibility to modify tracking without code changes, easier debugging, and future-proofing for additional tags. + +**Q: Does GTM slow down my website?** +A: GTM loads asynchronously and typically has minimal impact. Proper implementation with optimized tags can actually improve performance vs hardcoded tags. + +**Q: Can I use GTM on a single-page application (SPA)?** +A: Yes! Use History Change triggers for route changes and implement data layer pushes for virtual page views. See **gtm-datalayer** and **gtm-triggers** skills. + +**Q: How do I test GTM without affecting production data?** +A: Use Preview mode to test before publishing, use development environments, configure filters in analytics platforms to exclude test traffic. + +**Q: Can multiple people work on GTM at once?** +A: Yes, use Workspaces to allow simultaneous development. Each person works in their own workspace and merges changes when ready. + +**Q: What's the difference between GTM web and server-side containers?** +A: Web containers run in the browser (client-side), server-side containers run on your server infrastructure for enhanced privacy, first-party data, and performance. + +## References +- **references/gtm-overview.md** - Detailed GTM fundamentals and architecture + +## Integration with Other Skills +All GTM skills work together for comprehensive GTM implementation: +- **gtm-setup** - Initial container setup +- **gtm-tags** - Tag configuration +- **gtm-triggers** - Trigger configuration +- **gtm-variables** - Variable configuration +- **gtm-datalayer** - Data layer implementation +- **gtm-debugging** - Testing and troubleshooting +- **gtm-best-practices** - Optimization and standards +- **gtm-custom-templates** - Template development +- **gtm-api** - Automation and programmatic access + +## Quick Decision Tree + +**"I want to..."** +- Install GTM → **gtm-setup** +- Add a tracking tag → **gtm-tags** +- Track clicks or forms → **gtm-triggers** +- Capture dynamic data → **gtm-variables** +- Send custom data → **gtm-datalayer** +- Fix tags not firing → **gtm-debugging** +- Optimize performance → **gtm-best-practices** +- Build custom template → **gtm-custom-templates** +- Automate GTM management → **gtm-api** +- General GTM questions → **gtm-general** (this skill) + +## Best Practices + +### Planning Your Implementation +1. **Define tracking requirements** - What data do you need? +2. **Plan data layer structure** - How will data be passed? +3. **Choose container type** - Web, mobile, or server-side? +4. **Set up environments** - Development, staging, production +5. **Establish naming conventions** - Consistent tag/trigger/variable names +6. **Document your setup** - Version notes, workspace purposes +7. **Train your team** - Ensure everyone understands GTM workflows + +### Ongoing Management +- Test all changes in Preview mode before publishing +- Use descriptive version notes when publishing +- Regular container audits to remove unused elements +- Monitor tag performance and page load impact +- Keep security and privacy top of mind +- Stay updated on GTM new features and best practices + +See **gtm-best-practices** skill for detailed guidance. diff --git a/skills/gtm-general/references/gtm-overview.md b/skills/gtm-general/references/gtm-overview.md new file mode 100644 index 0000000..d957ce4 --- /dev/null +++ b/skills/gtm-general/references/gtm-overview.md @@ -0,0 +1,59 @@ +# About Google Tag Manager + +**Source**: https://developers.google.com/tag-platform/tag-manager +**Extracted**: 2025-01-09 + +Google Tag Manager is a tag management system that lets you configure and deploy tags on your website or mobile app, from an easy-to-use web interface. You can use Tag Manager to optimize your tag deployment, troubleshoot configuration errors, and modify tags that are already deployed. + +## Why Use Google Tag Manager + +Here are some reasons to use Tag Manager: + +- Support for tag organization. +- Version control. +- Support for multiple tag types, including the Google tag. +- Community-developed tag templates. +- Enterprise collaboration. +- Security features. + +## Deploy for Web Pages + +Use Tag Manager to manage tags (such as measurement and marketing optimization JavaScript tags) on your site. Without editing your site code, use Tag Manager to add and update Google Ads, Google Analytics, Floodlight, and third-party tags. + +**Related:** [Get started for websites](https://developers.google.com/tag-platform/tag-manager/web) + +## Deploy for Mobile Apps + +Use Google Tag Manager to manage analytics and advertising tools in your apps without having to rebuild and resubmit the applications' binaries to app marketplaces. + +**Related:** +- [Get started for Android](https://developers.google.com/tag-platform/tag-manager/android/v5) +- [Get started for iOS](https://developers.google.com/tag-platform/tag-manager/ios/v5) + +## Server-Side Tagging + +Server-Side Tagging allows you to move tag code off of your website or app and into the Cloud. Server-Side Tagging can help you improve the performance and security of your website or mobile app while serving all dependent assets from your own domain. + +**Related:** [Get started with Server-Side Tagging](https://developers.google.com/tag-platform/tag-manager/server-side) + +## Create Custom Templates + +Build your own tag and variable templates in Tag Manager. Submit templates to the Community Template Gallery and make them available worldwide. + +**Related:** +- [Quick start guide](https://developers.google.com/tag-platform/tag-manager/templates) +- [Submit templates to the Community Template Gallery](https://developers.google.com/tag-platform/tag-manager/templates/gallery) + +## Automate Configuration with the REST API + +The Google Tag Manager API provides access to Tag Manager configuration data for an authorized user. Use the API to manage accounts, containers and container versions, tags, rules, triggers, variables, and user permissions. + +**Related:** [Get started with the REST API](https://developers.google.com/tag-platform/tag-manager/api/v2/devguide) + +## Additional Resources + +For more information about Tag Manager features and benefits, visit the following resources: + +- [Product site](https://marketingplatform.google.com/about/tag-manager/) +- [Help center](https://support.google.com/tagmanager/) +- [Help community](https://support.google.com/tagmanager/community) diff --git a/skills/gtm-setup/SKILL.md b/skills/gtm-setup/SKILL.md new file mode 100644 index 0000000..f21fb38 --- /dev/null +++ b/skills/gtm-setup/SKILL.md @@ -0,0 +1,64 @@ +--- +name: gtm-setup +description: Expert guidance for Google Tag Manager container setup, installation, account management, workspaces, environments, and version publishing. Use when setting up new GTM containers, installing GTM snippets, managing workspaces, configuring environments, or publishing GTM versions. +--- + +# GTM Container Setup & Management + +## Overview +This skill provides expertise for setting up and managing Google Tag Manager containers, including installation, workspace management, and deployment workflows. + +## When to Use This Skill +Invoke this skill when: +- Setting up a new GTM container (web, mobile, server-side) +- Installing GTM snippet code +- Managing workspaces for different projects +- Configuring environments (Development, Staging, Production) +- Publishing GTM versions +- Managing container permissions and user access +- Setting up multiple containers on one site + +## Container Types + +### Web Containers +- Install GTM snippet in `` and `` +- Configure container for single-page applications (SPAs) +- Set up multiple containers on one site +- Manage container permissions and user access + +### Server-Side Containers +- Deploy to Cloudflare Workers, Google Cloud, or other platforms +- Configure server-side clients and tags +- Set up custom tag templates for server endpoints +- Implement first-party data collection + +### Mobile Containers +- Configure Firebase integration +- Set up mobile-specific triggers and variables + +## Workspace Management +- Use workspaces for different projects +- Create versions with detailed notes +- Compare versions and test in different environments +- Maintain version history and rollback capability + +## Publishing Workflow +1. Create changes in workspace +2. Test in Preview mode +3. Create version with detailed notes +4. Publish to appropriate environment +5. Verify deployment + +## References +- **references/gtm-overview.md** - GTM fundamentals, architecture, account structure + +## Integration with Other Skills +- **gtm-general** - General GTM guidance and concepts +- **gtm-tags** - Configure tags for tracking +- **gtm-triggers** - Set up triggers for tags +- **gtm-variables** - Create variables for dynamic data +- **gtm-debugging** - Debug and test your GTM setup +- **gtm-best-practices** - Follow GTM implementation best practices +- **gtm-datalayer** - Implement data layer for tracking +- **gtm-custom-templates** - Build custom templates +- **gtm-api** - Programmatic container management diff --git a/skills/gtm-setup/references/gtm-overview.md b/skills/gtm-setup/references/gtm-overview.md new file mode 100644 index 0000000..d957ce4 --- /dev/null +++ b/skills/gtm-setup/references/gtm-overview.md @@ -0,0 +1,59 @@ +# About Google Tag Manager + +**Source**: https://developers.google.com/tag-platform/tag-manager +**Extracted**: 2025-01-09 + +Google Tag Manager is a tag management system that lets you configure and deploy tags on your website or mobile app, from an easy-to-use web interface. You can use Tag Manager to optimize your tag deployment, troubleshoot configuration errors, and modify tags that are already deployed. + +## Why Use Google Tag Manager + +Here are some reasons to use Tag Manager: + +- Support for tag organization. +- Version control. +- Support for multiple tag types, including the Google tag. +- Community-developed tag templates. +- Enterprise collaboration. +- Security features. + +## Deploy for Web Pages + +Use Tag Manager to manage tags (such as measurement and marketing optimization JavaScript tags) on your site. Without editing your site code, use Tag Manager to add and update Google Ads, Google Analytics, Floodlight, and third-party tags. + +**Related:** [Get started for websites](https://developers.google.com/tag-platform/tag-manager/web) + +## Deploy for Mobile Apps + +Use Google Tag Manager to manage analytics and advertising tools in your apps without having to rebuild and resubmit the applications' binaries to app marketplaces. + +**Related:** +- [Get started for Android](https://developers.google.com/tag-platform/tag-manager/android/v5) +- [Get started for iOS](https://developers.google.com/tag-platform/tag-manager/ios/v5) + +## Server-Side Tagging + +Server-Side Tagging allows you to move tag code off of your website or app and into the Cloud. Server-Side Tagging can help you improve the performance and security of your website or mobile app while serving all dependent assets from your own domain. + +**Related:** [Get started with Server-Side Tagging](https://developers.google.com/tag-platform/tag-manager/server-side) + +## Create Custom Templates + +Build your own tag and variable templates in Tag Manager. Submit templates to the Community Template Gallery and make them available worldwide. + +**Related:** +- [Quick start guide](https://developers.google.com/tag-platform/tag-manager/templates) +- [Submit templates to the Community Template Gallery](https://developers.google.com/tag-platform/tag-manager/templates/gallery) + +## Automate Configuration with the REST API + +The Google Tag Manager API provides access to Tag Manager configuration data for an authorized user. Use the API to manage accounts, containers and container versions, tags, rules, triggers, variables, and user permissions. + +**Related:** [Get started with the REST API](https://developers.google.com/tag-platform/tag-manager/api/v2/devguide) + +## Additional Resources + +For more information about Tag Manager features and benefits, visit the following resources: + +- [Product site](https://marketingplatform.google.com/about/tag-manager/) +- [Help center](https://support.google.com/tagmanager/) +- [Help community](https://support.google.com/tagmanager/community) diff --git a/skills/gtm-tags/SKILL.md b/skills/gtm-tags/SKILL.md new file mode 100644 index 0000000..612600f --- /dev/null +++ b/skills/gtm-tags/SKILL.md @@ -0,0 +1,168 @@ +--- +name: gtm-tags +description: Expert guidance for configuring Google Tag Manager tags including GA4 configuration tags, GA4 event tags, Google Ads conversion tracking, Google Ads remarketing, custom HTML tags, third-party marketing tags (Facebook Pixel, LinkedIn Insight, TikTok Pixel), community template tags, tag sequencing, tag firing priority, consent settings, and tag timeout configuration. Use when creating tags, configuring GA4 tags, setting up Google Ads tracking, implementing marketing pixels, working with custom HTML, configuring tag firing order, setting up tag dependencies, implementing consent mode for tags, or troubleshooting tag configuration issues. +--- + +# GTM Tags Configuration + +## Overview +Expert guidance for configuring all types of tags in Google Tag Manager, from Google Analytics 4 and Google Ads to custom HTML and third-party marketing platforms. + +## When to Use This Skill +Invoke this skill when: +- Creating or configuring GTM tags of any type +- Setting up GA4 Configuration or Event tags +- Implementing Google Ads conversion tracking or remarketing +- Adding third-party marketing tags (Facebook, LinkedIn, TikTok, etc.) +- Writing custom HTML tags +- Configuring tag firing priority and sequencing +- Setting up tag timeout or consent settings +- Implementing tag dependencies or firing conditions +- Troubleshooting why a tag isn't configured correctly +- Optimizing tag configuration for performance + +## Common Tag Types + +### Google Analytics 4 Tags +- **GA4 Configuration Tag** - Base tag for GA4 tracking +- **GA4 Event Tag** - Custom events and conversions +- Event parameters and user properties +- E-commerce events (purchase, add_to_cart, view_item, etc.) +- Debug mode configuration + +### Google Ads Tags +- **Conversion Tracking** - Track conversions and assign values +- **Remarketing** - Build audiences for retargeting +- Enhanced conversions setup +- Conversion linker tag + +### Marketing Platform Tags +- Facebook Pixel (Meta Pixel) +- LinkedIn Insight Tag +- TikTok Pixel +- Twitter/X Pixel +- Pinterest Tag +- Snapchat Pixel + +### Custom Tags +- Custom HTML tags for any JavaScript +- Custom Image tags +- Community Template Gallery tags + +## Tag Configuration Settings + +### Core Settings +- Tag Type selection +- Configuration fields (varies by tag type) +- Trigger assignment (when the tag fires) +- Exception configuration (when NOT to fire) + +### Advanced Settings +- **Tag Firing Priority** - Control execution order (higher numbers fire first) +- **Tag Sequencing** - Setup tags that must fire before/after this tag +- **Tag Firing Options** - Once per event, once per page, unlimited +- **Consent Settings** - Require consent for tag firing +- **Tag Scheduling** - Set start/end dates for tag activity + +### Performance Settings +- **Timeout** - Maximum time to wait for tag to complete +- **Advanced Tag Firing** - Fire in DOM Ready, Page View, or Window Loaded + +## Common Workflows + +### Create a GA4 Event Tag +1. Click "New Tag" in GTM workspace +2. Select "Google Analytics: GA4 Event" tag type +3. Enter Measurement ID or reference GA4 Configuration tag +4. Set Event Name (e.g., "form_submit") +5. Add Event Parameters as needed +6. Select trigger (when to fire the tag) +7. Save and test in Preview mode + +### Set Up Google Ads Conversion Tag +1. Create new tag → Google Ads Conversion Tracking +2. Enter Conversion ID and Conversion Label from Google Ads +3. Set conversion value (static or variable) +4. Configure transaction ID for deduplication +5. Assign trigger (e.g., purchase completion page) +6. Test and publish + +### Add Custom HTML Tag +1. Create new tag → Custom HTML +2. Paste JavaScript code in HTML field +3. Ensure code is properly formatted +4. Consider security implications +5. Set appropriate trigger +6. Test thoroughly before publishing + +### Configure Tag Sequencing +1. Open tag that should fire first +2. Go to Advanced Settings → Tag Sequencing +3. Add "Setup Tag" (fires before this tag) +4. Add "Cleanup Tag" (fires after this tag) +5. Configure "pause" settings if needed +6. Test firing order in Preview mode + +## Best Practices + +### Tag Organization +- Use clear, consistent naming: `[Platform] - [Type] - [Description]` +- Example: `GA4 - Event - Form Submit`, `Google Ads - Conversion - Purchase` +- Group related tags in folders +- Document complex configurations in tag notes + +### Performance Optimization +- Prefer native tag templates over custom HTML +- Minimize custom JavaScript in tags +- Set appropriate timeouts (avoid overly long waits) +- Remove unused tags regularly +- Consolidate similar tags when possible + +### Security & Privacy +- Vet all custom HTML code carefully +- Review third-party tag templates before use +- Implement consent mode for privacy compliance +- Avoid sending PII (Personally Identifiable Information) +- Use tag permissions to control access + +## References +- **references/tags.md** - Comprehensive tag configuration guide with all tag types, parameters, and advanced configuration +- **references/google-rew-regular-expressions-syntax.txt** - RegEx syntax for tag firing conditions + +Search reference files for specific topics: +```bash +grep -r "GA4 Event" references/ +grep -r "consent" references/ +grep -r "Custom HTML" references/ +``` + +## Integration with Other Skills +- **gtm-triggers** - Configure when tags fire +- **gtm-variables** - Use variables in tag configuration +- **gtm-debugging** - Debug tags that aren't firing correctly +- **gtm-setup** - Container setup and workspace management +- **gtm-datalayer** - Advanced data layer implementation for event tags +- **gtm-best-practices** - Tag naming conventions and optimization strategies +- **gtm-custom-templates** - Build custom tag templates + +## Quick Reference + +### Common Tag Types Quick List +- GA4 Configuration +- GA4 Event +- Google Ads Conversion Tracking +- Google Ads Remarketing +- Conversion Linker +- Custom HTML +- Custom Image +- Community Template (various) + +### Tag Firing Priority +- Higher number = fires first (e.g., 100 fires before 50) +- Default priority = no value set +- Use for ensuring setup tags fire before tracking tags + +### Consent Settings +- **No additional consent required** - Tag fires immediately +- **Require additional consent for tag to fire** - Block until consent given +- Map to consent types (analytics_storage, ad_storage, etc.) diff --git a/skills/gtm-tags/references/google-rew-regular-expressions-syntax.txt b/skills/gtm-tags/references/google-rew-regular-expressions-syntax.txt new file mode 100644 index 0000000..44749b6 --- /dev/null +++ b/skills/gtm-tags/references/google-rew-regular-expressions-syntax.txt @@ -0,0 +1,462 @@ +RE2 regular expression syntax reference +-------------------------­-------­----- + +Single characters: +. any character, possibly including newline (s=true) +[xyz] character class +[^xyz] negated character class +\d Perl character class +\D negated Perl character class +[[:alpha:]] ASCII character class +[[:^alpha:]] negated ASCII character class +\pN Unicode character class (one-letter name) +\p{Greek} Unicode character class +\PN negated Unicode character class (one-letter name) +\P{Greek} negated Unicode character class + +Composites: +xy «x» followed by «y» +x|y «x» or «y» (prefer «x») + +Repetitions: +x* zero or more «x», prefer more +x+ one or more «x», prefer more +x? zero or one «x», prefer one +x{n,m} «n» or «n»+1 or ... or «m» «x», prefer more +x{n,} «n» or more «x», prefer more +x{n} exactly «n» «x» +x*? zero or more «x», prefer fewer +x+? one or more «x», prefer fewer +x?? zero or one «x», prefer zero +x{n,m}? «n» or «n»+1 or ... or «m» «x», prefer fewer +x{n,}? «n» or more «x», prefer fewer +x{n}? exactly «n» «x» +x{} (== x*) NOT SUPPORTED vim +x{-} (== x*?) NOT SUPPORTED vim +x{-n} (== x{n}?) NOT SUPPORTED vim +x= (== x?) NOT SUPPORTED vim + +Implementation restriction: The counting forms «x{n,m}», «x{n,}», and «x{n}» +reject forms that create a minimum or maximum repetition count above 1000. +Unlimited repetitions are not subject to this restriction. + +Possessive repetitions: +x*+ zero or more «x», possessive NOT SUPPORTED +x++ one or more «x», possessive NOT SUPPORTED +x?+ zero or one «x», possessive NOT SUPPORTED +x{n,m}+ «n» or ... or «m» «x», possessive NOT SUPPORTED +x{n,}+ «n» or more «x», possessive NOT SUPPORTED +x{n}+ exactly «n» «x», possessive NOT SUPPORTED + +Grouping: +(re) numbered capturing group (submatch) +(?Pre) named & numbered capturing group (submatch) +(?re) named & numbered capturing group (submatch) +(?'name're) named & numbered capturing group (submatch) NOT SUPPORTED +(?:re) non-capturing group +(?flags) set flags within current group; non-capturing +(?flags:re) set flags during re; non-capturing +(?#text) comment NOT SUPPORTED +(?|x|y|z) branch numbering reset NOT SUPPORTED +(?>re) possessive match of «re» NOT SUPPORTED +re@> possessive match of «re» NOT SUPPORTED vim +%(re) non-capturing group NOT SUPPORTED vim + +Flags: +i case-insensitive (default false) +m multi-line mode: «^» and «$» match begin/end line in addition to begin/end text (default false) +s let «.» match «\n» (default false) +U ungreedy: swap meaning of «x*» and «x*?», «x+» and «x+?», etc (default false) +Flag syntax is «xyz» (set) or «-xyz» (clear) or «xy-z» (set «xy», clear «z»). + +Empty strings: +^ at beginning of text or line («m»=true) +$ at end of text (like «\z» not «\Z») or line («m»=true) +\A at beginning of text +\b at ASCII word boundary («\w» on one side and «\W», «\A», or «\z» on the other) +\B not at ASCII word boundary +\G at beginning of subtext being searched NOT SUPPORTED pcre +\G at end of last match NOT SUPPORTED perl +\Z at end of text, or before newline at end of text NOT SUPPORTED +\z at end of text +(?=re) before text matching «re» NOT SUPPORTED +(?!re) before text not matching «re» NOT SUPPORTED +(?<=re) after text matching «re» NOT SUPPORTED +(? subroutine call NOT SUPPORTED +\g'name' subroutine call NOT SUPPORTED +\k named backreference NOT SUPPORTED +\k'name' named backreference NOT SUPPORTED +\lX lowercase «X» NOT SUPPORTED +\ux uppercase «x» NOT SUPPORTED +\L...\E lowercase text «...» NOT SUPPORTED +\K reset beginning of «$0» NOT SUPPORTED +\N{name} named Unicode character NOT SUPPORTED +\R line break NOT SUPPORTED +\U...\E upper case text «...» NOT SUPPORTED +\X extended Unicode sequence NOT SUPPORTED + +\%d123 decimal character 123 NOT SUPPORTED vim +\%xFF hex character FF NOT SUPPORTED vim +\%o123 octal character 123 NOT SUPPORTED vim +\%u1234 Unicode character 0x1234 NOT SUPPORTED vim +\%U12345678 Unicode character 0x12345678 NOT SUPPORTED vim + +Character class elements: +x single character +A-Z character range (inclusive) +\d Perl character class +[:foo:] ASCII character class «foo» +\p{Foo} Unicode character class «Foo» +\pF Unicode character class «F» (one-letter name) + +Named character classes as character class elements: +[\d] digits (== \d) +[^\d] not digits (== \D) +[\D] not digits (== \D) +[^\D] not not digits (== \d) +[[:name:]] named ASCII class inside character class (== [:name:]) +[^[:name:]] named ASCII class inside negated character class (== [:^name:]) +[\p{Name}] named Unicode property inside character class (== \p{Name}) +[^\p{Name}] named Unicode property inside negated character class (== \P{Name}) + +Perl character classes (all ASCII-only): +\d digits (== [0-9]) +\D not digits (== [^0-9]) +\s whitespace (== [\t\n\f\r ]) +\S not whitespace (== [^\t\n\f\r ]) +\w word characters (== [0-9A-Za-z_]) +\W not word characters (== [^0-9A-Za-z_]) + +\h horizontal space NOT SUPPORTED +\H not horizontal space NOT SUPPORTED +\v vertical space NOT SUPPORTED +\V not vertical space NOT SUPPORTED + +ASCII character classes: +[[:alnum:]] alphanumeric (== [0-9A-Za-z]) +[[:alpha:]] alphabetic (== [A-Za-z]) +[[:ascii:]] ASCII (== [\x00-\x7F]) +[[:blank:]] blank (== [\t ]) +[[:cntrl:]] control (== [\x00-\x1F\x7F]) +[[:digit:]] digits (== [0-9]) +[[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) +[[:lower:]] lower case (== [a-z]) +[[:print:]] printable (== [ -~] == [ [:graph:]]) +[[:punct:]] punctuation (== [!-/:-@[-`{-~]) +[[:space:]] whitespace (== [\t\n\v\f\r ]) +[[:upper:]] upper case (== [A-Z]) +[[:word:]] word characters (== [0-9A-Za-z_]) +[[:xdigit:]] hex digit (== [0-9A-Fa-f]) + +Unicode character class names--general category: +C other +Cc control +Cf format +Cn unassigned code points NOT SUPPORTED +Co private use +Cs surrogate +L letter +LC cased letter NOT SUPPORTED +L& cased letter NOT SUPPORTED +Ll lowercase letter +Lm modifier letter +Lo other letter +Lt titlecase letter +Lu uppercase letter +M mark +Mc spacing mark +Me enclosing mark +Mn non-spacing mark +N number +Nd decimal number +Nl letter number +No other number +P punctuation +Pc connector punctuation +Pd dash punctuation +Pe close punctuation +Pf final punctuation +Pi initial punctuation +Po other punctuation +Ps open punctuation +S symbol +Sc currency symbol +Sk modifier symbol +Sm math symbol +So other symbol +Z separator +Zl line separator +Zp paragraph separator +Zs space separator + +Unicode character class names--scripts: +Adlam +Ahom +Anatolian_Hieroglyphs +Arabic +Armenian +Avestan +Balinese +Bamum +Bassa_Vah +Batak +Bengali +Bhaiksuki +Bopomofo +Brahmi +Braille +Buginese +Buhid +Canadian_Aboriginal +Carian +Caucasian_Albanian +Chakma +Cham +Cherokee +Chorasmian +Common +Coptic +Cuneiform +Cypriot +Cypro_Minoan +Cyrillic +Deseret +Devanagari +Dives_Akuru +Dogra +Duployan +Egyptian_Hieroglyphs +Elbasan +Elymaic +Ethiopic +Georgian +Glagolitic +Gothic +Grantha +Greek +Gujarati +Gunjala_Gondi +Gurmukhi +Han +Hangul +Hanifi_Rohingya +Hanunoo +Hatran +Hebrew +Hiragana +Imperial_Aramaic +Inherited +Inscriptional_Pahlavi +Inscriptional_Parthian +Javanese +Kaithi +Kannada +Katakana +Kawi +Kayah_Li +Kharoshthi +Khitan_Small_Script +Khmer +Khojki +Khudawadi +Lao +Latin +Lepcha +Limbu +Linear_A +Linear_B +Lisu +Lycian +Lydian +Mahajani +Makasar +Malayalam +Mandaic +Manichaean +Marchen +Masaram_Gondi +Medefaidrin +Meetei_Mayek +Mende_Kikakui +Meroitic_Cursive +Meroitic_Hieroglyphs +Miao +Modi +Mongolian +Mro +Multani +Myanmar +Nabataean +Nag_Mundari +Nandinagari +New_Tai_Lue +Newa +Nko +Nushu +Nyiakeng_Puachue_Hmong +Ogham +Ol_Chiki +Old_Hungarian +Old_Italic +Old_North_Arabian +Old_Permic +Old_Persian +Old_Sogdian +Old_South_Arabian +Old_Turkic +Old_Uyghur +Oriya +Osage +Osmanya +Pahawh_Hmong +Palmyrene +Pau_Cin_Hau +Phags_Pa +Phoenician +Psalter_Pahlavi +Rejang +Runic +Samaritan +Saurashtra +Sharada +Shavian +Siddham +SignWriting +Sinhala +Sogdian +Sora_Sompeng +Soyombo +Sundanese +Syloti_Nagri +Syriac +Tagalog +Tagbanwa +Tai_Le +Tai_Tham +Tai_Viet +Takri +Tamil +Tangsa +Tangut +Telugu +Thaana +Thai +Tibetan +Tifinagh +Tirhuta +Toto +Ugaritic +Vai +Vithkuqi +Wancho +Warang_Citi +Yezidi +Yi +Zanabazar_Square + +Vim character classes: +\i identifier character NOT SUPPORTED vim +\I «\i» except digits NOT SUPPORTED vim +\k keyword character NOT SUPPORTED vim +\K «\k» except digits NOT SUPPORTED vim +\f file name character NOT SUPPORTED vim +\F «\f» except digits NOT SUPPORTED vim +\p printable character NOT SUPPORTED vim +\P «\p» except digits NOT SUPPORTED vim +\s whitespace character (== [ \t]) NOT SUPPORTED vim +\S non-white space character (== [^ \t]) NOT SUPPORTED vim +\d digits (== [0-9]) vim +\D not «\d» vim +\x hex digits (== [0-9A-Fa-f]) NOT SUPPORTED vim +\X not «\x» NOT SUPPORTED vim +\o octal digits (== [0-7]) NOT SUPPORTED vim +\O not «\o» NOT SUPPORTED vim +\w word character vim +\W not «\w» vim +\h head of word character NOT SUPPORTED vim +\H not «\h» NOT SUPPORTED vim +\a alphabetic NOT SUPPORTED vim +\A not «\a» NOT SUPPORTED vim +\l lowercase NOT SUPPORTED vim +\L not lowercase NOT SUPPORTED vim +\u uppercase NOT SUPPORTED vim +\U not uppercase NOT SUPPORTED vim +\_x «\x» plus newline, for any «x» NOT SUPPORTED vim + +Vim flags: +\c ignore case NOT SUPPORTED vim +\C match case NOT SUPPORTED vim +\m magic NOT SUPPORTED vim +\M nomagic NOT SUPPORTED vim +\v verymagic NOT SUPPORTED vim +\V verynomagic NOT SUPPORTED vim +\Z ignore differences in Unicode combining characters NOT SUPPORTED vim + +Magic: +(?{code}) arbitrary Perl code NOT SUPPORTED perl +(??{code}) postponed arbitrary Perl code NOT SUPPORTED perl +(?n) recursive call to regexp capturing group «n» NOT SUPPORTED +(?+n) recursive call to relative group «+n» NOT SUPPORTED +(?-n) recursive call to relative group «-n» NOT SUPPORTED +(?C) PCRE callout NOT SUPPORTED pcre +(?R) recursive call to entire regexp (== (?0)) NOT SUPPORTED +(?&name) recursive call to named group NOT SUPPORTED +(?P=name) named backreference NOT SUPPORTED +(?P>name) recursive call to named group NOT SUPPORTED +(?(cond)true|false) conditional branch NOT SUPPORTED +(?(cond)true) conditional branch NOT SUPPORTED +(*ACCEPT) make regexps more like Prolog NOT SUPPORTED +(*COMMIT) NOT SUPPORTED +(*F) NOT SUPPORTED +(*FAIL) NOT SUPPORTED +(*MARK) NOT SUPPORTED +(*PRUNE) NOT SUPPORTED +(*SKIP) NOT SUPPORTED +(*THEN) NOT SUPPORTED +(*ANY) set newline convention NOT SUPPORTED +(*ANYCRLF) NOT SUPPORTED +(*CR) NOT SUPPORTED +(*CRLF) NOT SUPPORTED +(*LF) NOT SUPPORTED +(*BSR_ANYCRLF) set \R convention NOT SUPPORTED pcre +(*BSR_UNICODE) NOT SUPPORTED pcre diff --git a/skills/gtm-tags/references/tags.md b/skills/gtm-tags/references/tags.md new file mode 100644 index 0000000..6a7992b --- /dev/null +++ b/skills/gtm-tags/references/tags.md @@ -0,0 +1,1039 @@ +# Google Tag Manager - Tags + +## Overview + +Tags are snippets of code or tracking pixels that send information from your website to third-party tools and platforms. In Google Tag Manager, tags are one of the three core components (Tags, Triggers, Variables) that make up your tracking implementation. + +**What Tags Do:** + +- Send data to analytics platforms (Google Analytics 4, Adobe Analytics) +- Track conversions for advertising platforms (Google Ads, Facebook, LinkedIn) +- Load third-party marketing and analytics scripts +- Execute custom JavaScript code +- Track custom events and user interactions + +## JavaScript Constraint for Custom HTML Tags + +**CRITICAL:** Google Tag Manager uses **ECMAScript 5 (ES5)** for JavaScript code in Custom HTML tags and Custom JavaScript variables, NOT modern ES6+. + +### ES6 Features NOT Supported + +```javascript +// ❌ WILL FAIL - ES6 syntax +const myVar = 'value'; +let count = 0; +const arrow = () => console.log('test'); +const template = `Hello ${name}`; +const [first, second] = array; +const {property} = object; + +// ✅ CORRECT - ES5 syntax +var myVar = 'value'; +var count = 0; +var regularFunc = function() { console.log('test'); }; +var concatenated = 'Hello ' + name; +var first = array[0]; +var second = array[1]; +var property = object.property; +``` + +### Workarounds for Modern JavaScript + +**Option 1: Transpilation (Recommended)** + +Use [Babel](https://babeljs.io/repl) to convert ES6+ to ES5 before pasting into GTM. + +**Option 2: text/gtmscript Tag** + +```html + +``` + +**Caution:** No syntax validation in GTM UI, harder to debug, only use when necessary. + +**See Also:** [Best Practices - JavaScript in GTM](./best-practices.md#javascript-in-google-tag-manager-es5-requirement) for detailed ES5 guidelines. + +## Tag Basics + +### How Tags Work + +1. User visits a webpage with GTM container +2. GTM evaluates triggers to determine which tags should fire +3. Matching tags execute and send data to third-party platforms +4. Data is received by analytics/marketing tools + +### Tag Lifecycle + +``` +Page Load → GTM Container Loads → Triggers Evaluate → Tags Fire → Data Sent +``` + +### Tag Components + +- **Tag Type** - Defines what the tag does (GA4 Event, Custom HTML, etc.) +- **Tag Configuration** - Settings and parameters specific to the tag type +- **Triggering** - When the tag should fire (page view, click, custom event) +- **Advanced Settings** - Tag sequencing, firing options, consent settings + +## Built-in Tag Types + +GTM provides pre-configured tag templates for popular platforms. These are optimized, maintained by Google, and recommended over custom implementations. + +### GA4 Configuration Tag + +Sets up Google Analytics 4 tracking and defines default configuration for all GA4 events. + +**Purpose:** +- Initialize GA4 on your site +- Set Measurement ID +- Configure default parameters +- Define user properties +- Configure enhanced measurement settings + +**Configuration:** + +``` +Measurement ID: G-XXXXXXXXXX (use a Constant variable) +Configuration Settings: + - send_page_view: true/false + - Fields to Set: session_id, user_id, etc. + - User Properties: user_type, membership_level, etc. +``` + +**Example:** + +``` +Tag Name: GA4 - Config - All Pages +Tag Type: Google Analytics: GA4 Configuration +Measurement ID: {{Constant - GA4 Measurement ID}} +Trigger: All Pages +``` + +**Best Practice:** Only ONE GA4 Configuration tag per Measurement ID. All GA4 Event tags reference this configuration. + +### GA4 Event Tag + +Sends individual events to Google Analytics 4. + +**Purpose:** +- Track user interactions (button clicks, form submissions) +- Track custom events (purchases, sign-ups, video plays) +- Send additional event parameters + +**Configuration:** + +``` +Configuration Tag: {{GA4 Configuration Tag}} +Event Name: purchase (recommended events) or custom_event_name +Event Parameters: + - currency: USD + - value: 99.99 + - transaction_id: T12345 + - items: [array of item objects] +``` + +**Recommended Events:** + +- `purchase` - Completed transaction +- `add_to_cart` - Item added to cart +- `begin_checkout` - Checkout process started +- `generate_lead` - Lead form submitted +- `login` - User logged in +- `sign_up` - User registered +- `view_item` - Product page viewed + +**Example:** + +```javascript +// Custom HTML implementation (ES5) + +``` + +### Google Ads Conversion Tracking + +Tracks conversions for Google Ads campaigns. + +**Purpose:** +- Track conversion actions (purchases, sign-ups, calls) +- Measure ad performance and ROI +- Enable conversion-based bidding + +**Configuration:** + +``` +Conversion ID: AW-XXXXXXXXXX +Conversion Label: unique_conversion_label +Conversion Value: {{Transaction Value}} or fixed value +Currency Code: USD +Transaction ID: {{Order ID}} (for deduplication) +``` + +**Example:** + +``` +Tag Name: Google Ads - Conversion - Purchase +Conversion ID: AW-123456789 +Conversion Label: abc123xyz +Conversion Value: {{DLV - Purchase Value}} +Currency Code: USD +Trigger: Custom Event - purchase +``` + +### Google Ads Remarketing + +Creates audiences for Google Ads remarketing campaigns. + +**Purpose:** +- Build remarketing audiences +- Track user behavior for ad targeting +- Send dynamic remarketing parameters + +**Configuration:** + +``` +Conversion ID: AW-XXXXXXXXXX +Custom Parameters: + - ecomm_prodid: product SKUs + - ecomm_pagetype: home, product, cart, purchase + - ecomm_totalvalue: cart/order value +``` + +### Floodlight Counter/Sales Tags + +Tracks conversions and activities for Campaign Manager 360 and Display & Video 360. + +**Counter Tag:** +- Tracks activities (page views, sign-ups) +- No transaction data + +**Sales Tag:** +- Tracks revenue-generating actions +- Includes transaction value and order ID + +**Configuration:** + +``` +Advertiser ID: 12345678 +Group Tag String: group123 +Activity Tag String: activity456 +Counting Method: Standard/Unique/Per Session +Transaction Data (Sales only): + - Revenue: {{Transaction Value}} + - Order ID: {{Order ID}} +``` + +### Custom HTML Tag + +Executes custom HTML and JavaScript code on your page. + +**Purpose:** +- Implement third-party tracking pixels not available as built-in tags +- Execute custom JavaScript logic +- Load external scripts +- Modify page elements + +**IMPORTANT:** Custom HTML tags require ES5 JavaScript syntax (see section above). + +**Configuration:** + +```html + +``` + +**Advanced HTML Options:** + +- **Support document.write** - Enable for legacy scripts (not recommended) +- **Convert null and undefined** - Replace with empty strings in variable outputs + +**Loading External Scripts:** + +```html + +``` + +**Security Best Practices:** + +- Never use `eval()` with user input +- Validate external script sources +- Avoid `document.write()` when possible +- Review all custom code before deployment + +### Custom Image Tag + +Fires a tracking pixel (1x1 image) to a specified URL. + +**Purpose:** +- Implement simple pixel-based tracking +- Track page views to external platforms +- Send data via URL parameters + +**Configuration:** + +``` +Image URL: https://tracking.example.com/pixel.gif?event=pageview&page={{Page URL}} +Cache Buster: true (adds random number to prevent caching) +``` + +**Example:** + +``` +Tag Name: Custom Pixel - Third Party Tracker +Image URL: https://analytics.thirdparty.com/track? + event=conversion + &value={{Transaction Value}} + &id={{Transaction ID}} +Trigger: Custom Event - purchase +``` + +**When to Use:** + +- Third-party platform only provides pixel tracking +- Lightweight tracking without JavaScript +- Cross-domain tracking scenarios + +### Conversion Linker Tag + +Stores conversion click information in first-party cookies for improved conversion measurement. + +**Purpose:** +- Improve conversion tracking accuracy +- Store click IDs in first-party cookies +- Enable cross-domain conversion tracking +- Support browser privacy restrictions + +**Configuration:** + +``` +Link Domains: example.com, shop.example.com +``` + +**Best Practice:** Fire on All Pages before any conversion tracking tags. + +**Example:** + +``` +Tag Name: Conversion Linker - All Pages +Tag Type: Conversion Linker +Trigger: All Pages +Tag Firing Options: Once per page +Tag Firing Priority: 100 (fire first) +``` + +### Additional Built-in Tag Types + +**LinkedIn Insight Tag** +- Track conversions for LinkedIn Ads +- Build matched audiences + +**Twitter Universal Website Tag** +- Track conversions for Twitter/X Ads +- Measure campaign performance + +**Microsoft Advertising UET Tag** +- Universal Event Tracking for Microsoft Advertising +- Conversion and remarketing tracking + +**TikTok Pixel** +- Track events for TikTok Ads +- Build custom audiences + +**Hotjar Tracking Code** +- Heatmaps and session recording +- User behavior analytics + +## Tag Configuration + +### Tag Setup Process + +1. **Choose Tag Type** - Select built-in tag or custom template +2. **Configure Tag Parameters** - Enter required settings (IDs, values, etc.) +3. **Set Up Triggering** - Define when tag should fire +4. **Configure Advanced Settings** (optional) + - Tag sequencing + - Firing options + - Consent settings + - Custom event name +5. **Save and Test** - Use Preview mode to verify + +### Tag Parameters + +Most tags require specific parameters: + +**Common Parameters:** + +- **Account/Container IDs** - Platform identifiers (GA4 Measurement ID, Ads Conversion ID) +- **Event Names** - Action being tracked +- **Event Parameters** - Additional context (value, currency, transaction_id) +- **User Data** - User properties or identifiers (never PII) + +**Using Variables in Parameters:** + +``` +Measurement ID: {{Constant - GA4 Measurement ID}} +Event Name: {{Custom Event}} +Transaction Value: {{DLV - Purchase Value}} +Currency: {{DLV - Currency Code}} +``` + +### Advanced Settings + +#### Tag Firing Options + +**Once per page:** +- Tag fires only once, even if trigger fires multiple times +- Use for: Page view tags, configuration tags + +**Once per event:** +- Tag fires once per unique event +- Use for: Tags that shouldn't duplicate + +**Unlimited:** +- Tag fires every time trigger matches +- Use for: Click tracking, user interaction tags + +#### Firing Priority + +Controls the order in which tags fire when multiple tags have the same trigger. + +``` +Priority: 100 (higher numbers fire first) +Default: 0 +``` + +**Use Cases:** + +- Fire consent tag (priority 100) before analytics tags (priority 0) +- Load configuration tag before event tags +- Ensure data layer is ready before reading values + +**Example:** + +``` +Priority 100: Consent Management Tag +Priority 50: GA4 Configuration Tag +Priority 25: Data Layer Preparation Tag +Priority 0: All other tags (default) +``` + +#### Tag Firing Schedule + +Restrict when a tag can fire based on date/time. + +``` +Start Date: 2024-01-01 00:00 +End Date: 2024-12-31 23:59 +``` + +**Use Cases:** + +- Seasonal campaign tracking +- Limited-time promotions +- A/B test windows +- Temporary tracking implementations + +## Tag Firing + +### How Tags Fire + +Tags fire when ALL of the following conditions are met: + +1. **Triggering condition matches** - At least one firing trigger evaluates to true +2. **No blocking triggers** - No exception triggers match +3. **Consent granted** (if consent settings configured) +4. **Tag schedule active** (if scheduling configured) + +### Trigger Association + +**Firing Triggers:** + +Tags fire when these triggers match. You can have multiple firing triggers (OR logic). + +``` +Trigger 1: All Pages +OR +Trigger 2: Custom Event - purchase +``` + +**Blocking Triggers (Exceptions):** + +Tags DON'T fire when these triggers match, even if a firing trigger matches. + +``` +Fire on: All Pages +EXCEPT: Page URL contains - /admin/ +``` + +**Example Use Case:** + +``` +Tag: GA4 - Event - Page View +Firing Triggers: + - All Pages + - History Change +Blocking Triggers: + - Page URL contains - /checkout/success (tracked separately) + - Page URL matches RegEx - /admin|dashboard/ +``` + +### Tag Evaluation Order + +1. **All triggers evaluate** - GTM checks all triggers in the container +2. **Tag priority determines order** - Higher priority tags fire first +3. **Tag sequencing executes** - Setup tags fire before main tags +4. **Async vs sync loading** - Tags load according to configuration + +## Tag Sequencing and Priorities + +### Tag Sequencing + +Force specific tags to fire in a defined order using tag sequencing. + +**Setup Tag:** + +Fires BEFORE the main tag. + +``` +Main Tag: GA4 - Event - Purchase +Setup Tag: Custom HTML - Prepare Data Layer + → Setup tag completes BEFORE main tag fires +``` + +**Cleanup Tag:** + +Fires AFTER the main tag completes. + +``` +Main Tag: Conversion Pixel - Purchase +Cleanup Tag: Custom HTML - Clear Transaction Data + → Cleanup tag fires AFTER main tag completes +``` + +**Configuration:** + +In tag's Advanced Settings: + +``` +Tag Sequencing: + ☑ Fire a tag before [tag name] fires - setup tag + ☑ Fire a tag after [tag name] fires - cleanup tag +``` + +**Use Cases:** + +- Prepare data layer before reading values +- Wait for external library to load +- Clear sensitive data after sending +- Initialize configuration before events + +**Example Implementation:** + +``` +Setup Tag: + Name: Custom HTML - Load External Library + HTML: + Trigger: All Pages + +Main Tag: + Name: Custom - Third Party Event + Tag Sequencing: Setup Tag → Custom HTML - Load External Library + Trigger: Custom Event - user_action + +Cleanup Tag: + Name: Custom HTML - Log Success + Tag Sequencing: Main Tag → Custom - Third Party Event + Trigger: (inherited from main tag) +``` + +### Tag Firing Priority + +**Default Priority:** 0 + +**Priority Range:** Any integer (negative to positive) + +**Execution:** Higher numbers fire first + +**Example Priority Scheme:** + +``` +Priority 100: Consent Management Platform +Priority 90: Conversion Linker +Priority 75: GA4 Configuration Tag +Priority 50: Data Layer Setup +Priority 25: Marketing/Advertising Tags +Priority 0: Standard Event Tags (default) +Priority -10: Debugging/Development Tags +``` + +**Best Practice:** + +Only use priority when order matters. Most tags don't need custom priority. + +## Blocking Triggers + +Blocking triggers (exceptions) prevent a tag from firing even when a firing trigger matches. + +### How Blocking Triggers Work + +``` +IF (Firing Trigger = TRUE) AND (Blocking Trigger = FALSE) + THEN Tag Fires +ELSE + Tag Does Not Fire +``` + +### Common Use Cases + +**Exclude Admin Pages:** + +``` +Firing Trigger: All Pages +Blocking Trigger: Page URL contains - /admin +``` + +**Exclude Internal Traffic:** + +``` +Firing Trigger: All Pages +Blocking Trigger: IP Address equals - 203.0.113.0 +``` + +**Exclude Test Environments:** + +``` +Firing Trigger: All Pages +Blocking Trigger: Page Hostname equals - dev.example.com +``` + +**Avoid Duplicate Tracking:** + +``` +Tag: GA4 - Event - Generic Button Click +Firing Trigger: Click - All Buttons +Blocking Trigger: Click Element ID equals - special-button + (special button tracked with dedicated tag) +``` + +### Multiple Blocking Triggers + +Multiple blocking triggers use OR logic: + +``` +Tag Fires IF: + Firing Trigger = TRUE + AND Blocking Trigger 1 = FALSE + AND Blocking Trigger 2 = FALSE +``` + +**Example:** + +``` +Tag: GA4 - Event - Page View +Firing Trigger: All Pages +Blocking Triggers: + - Page URL contains - /admin/ + - Page URL contains - /test/ + - Cookie - internal_user - equals - true +``` + +## Community Gallery Templates + +The Community Template Gallery provides pre-built tag templates created by third-party vendors and developers. + +### Benefits + +- Quick implementation of third-party tools +- Vendor-maintained and updated +- Sandboxed for security +- No custom HTML required + +### Popular Community Templates + +- **Cookiebot** - Cookie consent management +- **OneTrust** - Privacy and consent management +- **Matomo Analytics** - Open-source analytics +- **Segment** - Customer data platform +- **Amplitude** - Product analytics +- **Mixpanel** - Product analytics +- **Heap** - Automatic event tracking +- **Clarity** - Microsoft user behavior analytics + +### Using Community Templates + +1. **Tags → New → Discover more tag types in the Community Template Gallery** +2. **Search for template by name or vendor** +3. **Review permissions and security** +4. **Add to workspace** +5. **Configure template parameters** +6. **Set up triggers** +7. **Test and publish** + +### Template Security + +Community templates run in a **sandboxed environment** with restricted permissions: + +**Review before adding:** + +- Required permissions (access to cookies, pixels, data layer) +- Privacy policy and data handling +- Vendor reputation and reviews +- Template documentation + +**Permissions to watch for:** + +- Accesses global variables +- Sends data to URLs +- Accesses cookies +- Reads from data layer + +### Creating Custom Templates + +You can create your own custom tag templates using the Template Editor. + +**See:** `.claude/skills/gtm-custom-templates/` for comprehensive custom template documentation. + +## Consent Settings + +Google Consent Mode v2 allows tags to respect user consent preferences. + +### Consent Types + +- **ad_storage** - Advertising cookies and tracking +- **analytics_storage** - Analytics cookies +- **functionality_storage** - Functional cookies +- **personalization_storage** - Personalization cookies +- **security_storage** - Security-related cookies + +### Tag Consent Configuration + +In tag's Consent Settings: + +``` +Consent Overview: + - No additional consent required + - Require additional consent for tag to fire + +If "Require additional consent": + ☑ ad_storage + ☑ analytics_storage + +Tag fires only if: ad_storage = granted AND analytics_storage = granted +``` + +### Built-in Consent Behavior + +**GA4 Tags:** +- Automatically respect analytics_storage consent +- Send pings when consent denied (consent mode) + +**Google Ads Tags:** +- Automatically respect ad_storage consent +- Support conversion pings without cookies + +### Consent Mode Implementation + +```javascript +// Default consent state (before user choice) + + +// Update consent after user choice + +``` + +## Tag Testing and Debugging + +### Preview Mode + +**Enable Preview Mode:** + +1. Click "Preview" in GTM interface +2. Enter website URL +3. Debug panel opens alongside website + +**Debug Panel Sections:** + +- **Summary** - Overview of page events +- **Tags** - Tags Fired, Tags Not Fired +- **Data Layer** - Current data layer state +- **Variables** - Variable values for selected event +- **Errors** - JavaScript errors and issues + +### Verify Tag Firing + +**In Preview Mode:** + +1. Navigate to event (page view, click, etc.) +2. Check "Tags Fired" section +3. Click tag to see details +4. Verify tag configuration and variables + +**In Receiving Platform:** + +- **GA4** - Real-time reports, DebugView +- **Google Ads** - Conversion tracking status +- **Third-party platforms** - Platform-specific testing tools + +### Common Tag Issues + +**Tag Not Firing:** + +- Trigger conditions not met +- Blocking trigger active +- Consent not granted +- Tag schedule not active +- Tag paused + +**Tag Firing Multiple Times:** + +- Trigger firing multiple times +- Missing "Once per page" firing option +- Data layer push in loop + +**Wrong Data Sent:** + +- Variable not populated +- Incorrect variable reference +- Data layer structure mismatch +- Typo in parameter name + +### Browser Console Debugging + +```javascript +// Check if GTM loaded +console.log(google_tag_manager); + +// Check data layer +console.log(dataLayer); + +// Enable GTM debug messages +localStorage.setItem('gtm.debug', true); +``` + +### Google Tag Assistant + +Chrome extension for debugging tags: + +- Install [Google Tag Assistant](https://tagassistant.google.com/) +- Visit your website +- Click extension icon +- Review tag firing and errors + +## Best Practices + +### Tag Naming Conventions + +Use consistent, descriptive naming: + +``` +[Platform] - [Type] - [Purpose] - [Trigger Condition] +``` + +**Examples:** + +- `GA4 - Config - All Pages` +- `GA4 - Event - Purchase - Thank You Page` +- `Google Ads - Conversion - Form Submit - Contact` +- `Facebook - Pixel - Page View - All Pages` +- `Custom HTML - Load Chat Widget - All Pages` + +### Tag Organization + +**Use Folders:** + +``` +├── Analytics +│ ├── GA4 +│ └── Adobe Analytics +├── Advertising +│ ├── Google Ads +│ ├── Facebook +│ └── LinkedIn +├── Marketing +│ ├── Email Tracking +│ └── CRM Integration +└── Utilities + ├── Consent Management + └── Error Tracking +``` + +### Performance Optimization + +**Prefer Built-in Tags:** + +1. Built-in tags (GA4, Google Ads) +2. Community Gallery templates +3. Custom templates +4. Custom HTML (last resort) + +**Minimize Tag Count:** + +- Consolidate similar tags +- Use single configuration tag +- Remove unused tags +- Audit regularly + +**Async Loading:** + +- Keep GTM container async (default) +- Avoid document.write in custom HTML +- Load external scripts asynchronously + +**Tag Timeout:** + +- Set appropriate timeout in Container Settings (default: 2000ms) +- Prevents tags from blocking page indefinitely + +### Security Best Practices + +**Custom HTML Tags:** + +- Review all code before deployment +- Never use `eval()` with user input +- Validate external script sources +- Avoid loading untrusted scripts + +**Data Handling:** + +- Never send PII (emails, names, addresses) +- Hash sensitive identifiers +- Respect user consent preferences +- Follow privacy regulations (GDPR, CCPA) + +**Template Permissions:** + +- Review community template permissions +- Grant minimum necessary access +- Verify vendor reputation +- Monitor template updates + +### Testing Before Deployment + +**Pre-Publish Checklist:** + +- [ ] Preview mode testing completed +- [ ] All triggers fire correctly +- [ ] Data appears in receiving platforms +- [ ] No console errors +- [ ] Cross-browser testing done +- [ ] Mobile testing done +- [ ] Edge cases tested +- [ ] Team review completed + +### Version Control + +**Document Changes:** + +``` +Version: 2.1 - Add GA4 Enhanced Ecommerce + +Changes: +- Added GA4 purchase event tag +- Updated data layer variables for ecommerce +- Fixed duplicate page view issue + +Tags Added: +- GA4 - Event - Purchase - Thank You Page +- GA4 - Event - Add to Cart - Product Pages + +Tags Modified: +- GA4 - Config - All Pages (added user properties) + +Testing: +- Verified in GA4 DebugView +- Tested checkout flow end-to-end +- Confirmed data accuracy +``` + +**Regular Audits:** + +- Monthly review of tag firing +- Quarterly cleanup of unused tags +- Annual comprehensive audit +- Remove test/development tags from production + +## Resources + +### Official Documentation + +- [Google Tag Manager Developer Guide](https://developers.google.com/tag-platform/tag-manager) +- [GTM Help Center](https://support.google.com/tagmanager) +- [GA4 Tag Configuration](https://developers.google.com/tag-platform/gtagjs/reference/ga4-events) +- [Google Ads Conversion Tracking](https://support.google.com/google-ads/answer/6331314) + +### Related GTM Skills + +- [GTM Triggers](./triggers.md) - Comprehensive trigger documentation +- [GTM Variables](./variables.md) - Variable types and configuration +- [GTM Data Layer](../../gtm-datalayer/gtm-datalayer/references/datalayer-fundamentals.md) - Data layer implementation +- [GTM Best Practices](./best-practices.md) - Naming, organization, security +- [Custom Templates](../../gtm-custom-templates/gtm-custom-templates/references/custom-templates-guide.md) - Creating custom tag templates + +### Tools + +- [Google Tag Assistant](https://tagassistant.google.com/) +- [GA4 DebugView](https://support.google.com/analytics/answer/7201382) +- [Babel REPL](https://babeljs.io/repl) - ES6 to ES5 transpilation +- [GTM Preview Mode](https://support.google.com/tagmanager/answer/6107056) + +### Community + +- [GTM Community Forum](https://support.google.com/tagmanager/community) +- [Simo Ahava's Blog](https://www.simoahava.com/) +- [Analytics Mania](https://www.analyticsmania.com/) +- [MeasureSchool](https://measureschool.com/) diff --git a/skills/gtm-triggers/SKILL.md b/skills/gtm-triggers/SKILL.md new file mode 100644 index 0000000..e1851db --- /dev/null +++ b/skills/gtm-triggers/SKILL.md @@ -0,0 +1,271 @@ +--- +name: gtm-triggers +description: Expert guidance for configuring Google Tag Manager triggers including page view triggers, click triggers (all elements, just links), form submission triggers, scroll depth tracking, element visibility triggers, video triggers (YouTube), custom event triggers, timer triggers, history change triggers for SPAs, JavaScript error triggers, trigger conditions, RegEx patterns, trigger exceptions, auto-event variables, and combining multiple trigger conditions. Use when creating triggers, setting up click tracking, configuring form submit tracking, implementing scroll tracking, working with custom events, debugging trigger conditions, using RegEx in triggers, setting trigger exceptions, or troubleshooting why triggers aren't firing. +--- + +# GTM Triggers Configuration + +## Overview +Expert guidance for configuring all types of triggers in Google Tag Manager that control when tags fire, from simple page views to complex conditional triggers with RegEx patterns. + +## When to Use This Skill +Invoke this skill when: +- Creating or configuring GTM triggers +- Setting up page view or DOM-based triggers +- Implementing click tracking (buttons, links, elements) +- Configuring form submission tracking +- Setting up scroll depth or element visibility triggers +- Working with custom event triggers from data layer +- Creating timer or history change triggers +- Using RegEx patterns in trigger conditions +- Setting up trigger exceptions (when NOT to fire) +- Combining multiple conditions in triggers +- Debugging why triggers aren't firing +- Optimizing trigger performance + +## Trigger Types + +### Pageview Triggers +- **Page View** - Fires on every page load (including virtual pages) +- **DOM Ready** - Fires when DOM is ready (before images load) +- **Window Loaded** - Fires when page fully loads (including images) + +Use cases: +- Page View: Most common for tracking page views +- DOM Ready: Fire tags before full page load for performance +- Window Loaded: When you need all page resources loaded + +### Click Triggers +- **All Elements** - Track clicks on any element (buttons, divs, images, etc.) +- **Just Links** - Track clicks only on `` tags + +Auto-Event Variables available: +- Click Element, Click Classes, Click ID +- Click URL, Click Text +- Click Target + +### Form Triggers +- **Form Submission** - Fires when form is submitted + +Auto-Event Variables available: +- Form Element, Form Classes, Form ID +- Form Target, Form URL +- Form Text + +### User Engagement Triggers +- **Scroll Depth** - Track when users scroll to specific depths + - Vertical Scroll Depths: 25%, 50%, 75%, 90%, etc. + - Horizontal scrolling also supported +- **Element Visibility** - Fire when element becomes visible + - Minimum visibility percentage + - On-screen duration requirements +- **YouTube Video** - Track video plays, pauses, progress + - Requires YouTube videos to have enablejsapi=1 + +### Custom Event Triggers +- **Custom Event** - Fire when specific data layer event occurs +- Event name must match `dataLayer.push({'event': 'event_name'})` +- Most flexible trigger type for custom implementations + +### Timer Triggers +- **Timer** - Fire repeatedly at intervals +- Set interval (milliseconds) +- Set limit on number of times to fire +- Use for tracking time on page, polling, etc. + +### History Triggers +- **History Change** - Fire on URL changes without page reload +- Essential for Single Page Applications (SPAs) +- Tracks pushState, replaceState, and hash changes + +### Other Triggers +- **JavaScript Error** - Fire when JavaScript errors occur +- **Trigger Group** - Combine multiple triggers with AND logic +- **Window Resize** - Fire when browser window resizes (custom via Custom HTML) + +## Trigger Configuration + +### Basic Configuration +1. **Trigger Type** - Select the type of trigger +2. **Trigger Name** - Use clear naming convention +3. **Trigger Conditions** - Define when to fire +4. **Trigger Exceptions** - Define when NOT to fire + +### Trigger Conditions (Filters) +Combine multiple conditions with AND/OR logic: + +**Condition Structure:** +- Variable (e.g., Click URL, Page Path) +- Operator (equals, contains, matches RegEx, etc.) +- Value (static text or variable) + +**Common Operators:** +- `equals` - Exact match +- `contains` - Substring match +- `matches RegEx` - Pattern matching +- `starts with` - Prefix match +- `ends with` - Suffix match +- `less than` / `greater than` - Numeric comparison +- `CSS selector` - Match element with selector + +**Example Conditions:** +``` +Click URL contains /checkout +Page Path equals /thank-you +Click Classes matches RegEx ^btn-primary$ +Form ID equals contact-form +``` + +### Trigger Exceptions +Prevent trigger from firing in specific cases: +- Same format as conditions +- Useful for excluding specific pages, elements, or scenarios + +**Example:** +Trigger on all clicks EXCEPT: +- Click URL contains /admin +- Click Classes contains no-track + +### RegEx in Triggers +Use Regular Expressions for complex pattern matching: +- `.*` - Match any characters +- `^` - Start of string +- `$` - End of string +- `|` - OR operator +- `[a-z]` - Character range +- `\d` - Any digit + +**Examples:** +``` +Page Path matches RegEx: ^/(products|services)/.* +Click URL matches RegEx: \.(pdf|docx?)$ +Custom Event equals: purchase|checkout|lead +``` + +See references/google-rew-regular-expressions-syntax.txt for complete RegEx syntax. + +## Common Workflows + +### Create Click Trigger for Specific Button +1. Create new trigger → Click - All Elements +2. Set condition: Click Classes contains "cta-button" +3. Or use: Click ID equals "signup-btn" +4. Test in Preview mode by clicking button +5. Verify in Tags Fired section + +### Set Up Form Submission Trigger +1. Create new trigger → Form Submission +2. Set condition: Form ID equals "contact-form" +3. Or use: Page Path equals /contact +4. Set exception if needed (e.g., exclude test forms) +5. Test by submitting form in Preview mode + +### Configure Scroll Depth Trigger +1. Create new trigger → Scroll Depth +2. Select Vertical Scroll Depths +3. Choose percentages: 25, 50, 75, 90 +4. Set Page Path condition if needed (specific pages only) +5. Test by scrolling on page + +### Create Custom Event Trigger +1. Ensure data layer push is implemented: `dataLayer.push({'event': 'add_to_cart'})` +2. Create new trigger → Custom Event +3. Set Event name: add_to_cart +4. Add conditions as needed (e.g., fire only on specific pages) +5. Test in Preview mode, check Data Layer tab + +### Set Up History Change Trigger for SPA +1. Create new trigger → History Change +2. Add condition: Page Path matches RegEx (if needed) +3. Test by navigating within SPA +4. Verify trigger fires on URL changes without page reload + +## Best Practices + +### Trigger Naming +Use consistent format: `[Event Type] - [Description]` + +Examples: +- `Click - CTA Button` +- `Page View - Homepage` +- `Form Submit - Contact Form` +- `Custom Event - Add to Cart` + +### Trigger Optimization +- Use specific conditions to avoid over-firing +- Set exceptions to exclude unwanted scenarios +- Test thoroughly in Preview mode +- Use built-in variables when possible (faster than custom JS) +- Avoid overly complex RegEx patterns + +### Common Pitfalls +- **Click triggers not firing**: Element might be removed before click registers + - Solution: Use "Wait for Tags" or increase timeout +- **Form triggers not firing**: Form submits via AJAX without traditional submit + - Solution: Use custom event trigger instead +- **History triggers over-firing**: Fires on every URL parameter change + - Solution: Add specific Page Path conditions + +### Auto-Event Variables +Enable relevant built-in variables for triggers: +- Click triggers: Click Element, Click Classes, Click ID, Click URL, Click Text +- Form triggers: Form Element, Form Classes, Form ID, Form URL, Form Text +- Scroll triggers: Scroll Depth Threshold, Scroll Depth Units +- Video triggers: Video Provider, Video Status, Video URL, Video Title + +## References +- **references/triggers.md** - Comprehensive trigger configuration guide with all trigger types, conditions, and advanced patterns +- **references/google-rew-regular-expressions-syntax.txt** - RegEx syntax for trigger conditions + +Search reference files for specific topics: +```bash +grep -r "Custom Event" references/ +grep -r "scroll depth" references/ +grep -r "RegEx" references/ +``` + +## Integration with Other Skills +- **gtm-tags** - Configure tags that use these triggers +- **gtm-variables** - Use variables in trigger conditions +- **gtm-debugging** - Debug triggers that aren't firing +- **gtm-datalayer** - Implement custom events for custom event triggers +- **gtm-setup** - Container setup and built-in variables +- **gtm-best-practices** - Trigger naming conventions and optimization + +## Quick Reference + +### Trigger Type Selection Guide +- **Simple page tracking**: Page View +- **Button/link clicks**: Click triggers +- **Form submissions**: Form Submission +- **User engagement**: Scroll Depth, Element Visibility +- **Custom implementation**: Custom Event +- **SPA navigation**: History Change +- **Polling/time-based**: Timer +- **Error monitoring**: JavaScript Error + +### Common Condition Patterns +``` +// Homepage only +Page Path equals / + +// All product pages +Page Path starts with /products/ + +// PDF downloads +Click URL ends with .pdf + +// Multiple pages with RegEx +Page Path matches RegEx ^/(about|contact|services)$ + +// Exclude admin pages +Page Path does not contain /admin/ +``` + +### Debugging Checklist +- [ ] Trigger type is correct for event +- [ ] Trigger conditions are specific enough +- [ ] Built-in variables are enabled +- [ ] No conflicting trigger exceptions +- [ ] Testing in Preview mode +- [ ] Checking Variables tab for condition values diff --git a/skills/gtm-triggers/references/google-rew-regular-expressions-syntax.txt b/skills/gtm-triggers/references/google-rew-regular-expressions-syntax.txt new file mode 100644 index 0000000..44749b6 --- /dev/null +++ b/skills/gtm-triggers/references/google-rew-regular-expressions-syntax.txt @@ -0,0 +1,462 @@ +RE2 regular expression syntax reference +-------------------------­-------­----- + +Single characters: +. any character, possibly including newline (s=true) +[xyz] character class +[^xyz] negated character class +\d Perl character class +\D negated Perl character class +[[:alpha:]] ASCII character class +[[:^alpha:]] negated ASCII character class +\pN Unicode character class (one-letter name) +\p{Greek} Unicode character class +\PN negated Unicode character class (one-letter name) +\P{Greek} negated Unicode character class + +Composites: +xy «x» followed by «y» +x|y «x» or «y» (prefer «x») + +Repetitions: +x* zero or more «x», prefer more +x+ one or more «x», prefer more +x? zero or one «x», prefer one +x{n,m} «n» or «n»+1 or ... or «m» «x», prefer more +x{n,} «n» or more «x», prefer more +x{n} exactly «n» «x» +x*? zero or more «x», prefer fewer +x+? one or more «x», prefer fewer +x?? zero or one «x», prefer zero +x{n,m}? «n» or «n»+1 or ... or «m» «x», prefer fewer +x{n,}? «n» or more «x», prefer fewer +x{n}? exactly «n» «x» +x{} (== x*) NOT SUPPORTED vim +x{-} (== x*?) NOT SUPPORTED vim +x{-n} (== x{n}?) NOT SUPPORTED vim +x= (== x?) NOT SUPPORTED vim + +Implementation restriction: The counting forms «x{n,m}», «x{n,}», and «x{n}» +reject forms that create a minimum or maximum repetition count above 1000. +Unlimited repetitions are not subject to this restriction. + +Possessive repetitions: +x*+ zero or more «x», possessive NOT SUPPORTED +x++ one or more «x», possessive NOT SUPPORTED +x?+ zero or one «x», possessive NOT SUPPORTED +x{n,m}+ «n» or ... or «m» «x», possessive NOT SUPPORTED +x{n,}+ «n» or more «x», possessive NOT SUPPORTED +x{n}+ exactly «n» «x», possessive NOT SUPPORTED + +Grouping: +(re) numbered capturing group (submatch) +(?Pre) named & numbered capturing group (submatch) +(?re) named & numbered capturing group (submatch) +(?'name're) named & numbered capturing group (submatch) NOT SUPPORTED +(?:re) non-capturing group +(?flags) set flags within current group; non-capturing +(?flags:re) set flags during re; non-capturing +(?#text) comment NOT SUPPORTED +(?|x|y|z) branch numbering reset NOT SUPPORTED +(?>re) possessive match of «re» NOT SUPPORTED +re@> possessive match of «re» NOT SUPPORTED vim +%(re) non-capturing group NOT SUPPORTED vim + +Flags: +i case-insensitive (default false) +m multi-line mode: «^» and «$» match begin/end line in addition to begin/end text (default false) +s let «.» match «\n» (default false) +U ungreedy: swap meaning of «x*» and «x*?», «x+» and «x+?», etc (default false) +Flag syntax is «xyz» (set) or «-xyz» (clear) or «xy-z» (set «xy», clear «z»). + +Empty strings: +^ at beginning of text or line («m»=true) +$ at end of text (like «\z» not «\Z») or line («m»=true) +\A at beginning of text +\b at ASCII word boundary («\w» on one side and «\W», «\A», or «\z» on the other) +\B not at ASCII word boundary +\G at beginning of subtext being searched NOT SUPPORTED pcre +\G at end of last match NOT SUPPORTED perl +\Z at end of text, or before newline at end of text NOT SUPPORTED +\z at end of text +(?=re) before text matching «re» NOT SUPPORTED +(?!re) before text not matching «re» NOT SUPPORTED +(?<=re) after text matching «re» NOT SUPPORTED +(? subroutine call NOT SUPPORTED +\g'name' subroutine call NOT SUPPORTED +\k named backreference NOT SUPPORTED +\k'name' named backreference NOT SUPPORTED +\lX lowercase «X» NOT SUPPORTED +\ux uppercase «x» NOT SUPPORTED +\L...\E lowercase text «...» NOT SUPPORTED +\K reset beginning of «$0» NOT SUPPORTED +\N{name} named Unicode character NOT SUPPORTED +\R line break NOT SUPPORTED +\U...\E upper case text «...» NOT SUPPORTED +\X extended Unicode sequence NOT SUPPORTED + +\%d123 decimal character 123 NOT SUPPORTED vim +\%xFF hex character FF NOT SUPPORTED vim +\%o123 octal character 123 NOT SUPPORTED vim +\%u1234 Unicode character 0x1234 NOT SUPPORTED vim +\%U12345678 Unicode character 0x12345678 NOT SUPPORTED vim + +Character class elements: +x single character +A-Z character range (inclusive) +\d Perl character class +[:foo:] ASCII character class «foo» +\p{Foo} Unicode character class «Foo» +\pF Unicode character class «F» (one-letter name) + +Named character classes as character class elements: +[\d] digits (== \d) +[^\d] not digits (== \D) +[\D] not digits (== \D) +[^\D] not not digits (== \d) +[[:name:]] named ASCII class inside character class (== [:name:]) +[^[:name:]] named ASCII class inside negated character class (== [:^name:]) +[\p{Name}] named Unicode property inside character class (== \p{Name}) +[^\p{Name}] named Unicode property inside negated character class (== \P{Name}) + +Perl character classes (all ASCII-only): +\d digits (== [0-9]) +\D not digits (== [^0-9]) +\s whitespace (== [\t\n\f\r ]) +\S not whitespace (== [^\t\n\f\r ]) +\w word characters (== [0-9A-Za-z_]) +\W not word characters (== [^0-9A-Za-z_]) + +\h horizontal space NOT SUPPORTED +\H not horizontal space NOT SUPPORTED +\v vertical space NOT SUPPORTED +\V not vertical space NOT SUPPORTED + +ASCII character classes: +[[:alnum:]] alphanumeric (== [0-9A-Za-z]) +[[:alpha:]] alphabetic (== [A-Za-z]) +[[:ascii:]] ASCII (== [\x00-\x7F]) +[[:blank:]] blank (== [\t ]) +[[:cntrl:]] control (== [\x00-\x1F\x7F]) +[[:digit:]] digits (== [0-9]) +[[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) +[[:lower:]] lower case (== [a-z]) +[[:print:]] printable (== [ -~] == [ [:graph:]]) +[[:punct:]] punctuation (== [!-/:-@[-`{-~]) +[[:space:]] whitespace (== [\t\n\v\f\r ]) +[[:upper:]] upper case (== [A-Z]) +[[:word:]] word characters (== [0-9A-Za-z_]) +[[:xdigit:]] hex digit (== [0-9A-Fa-f]) + +Unicode character class names--general category: +C other +Cc control +Cf format +Cn unassigned code points NOT SUPPORTED +Co private use +Cs surrogate +L letter +LC cased letter NOT SUPPORTED +L& cased letter NOT SUPPORTED +Ll lowercase letter +Lm modifier letter +Lo other letter +Lt titlecase letter +Lu uppercase letter +M mark +Mc spacing mark +Me enclosing mark +Mn non-spacing mark +N number +Nd decimal number +Nl letter number +No other number +P punctuation +Pc connector punctuation +Pd dash punctuation +Pe close punctuation +Pf final punctuation +Pi initial punctuation +Po other punctuation +Ps open punctuation +S symbol +Sc currency symbol +Sk modifier symbol +Sm math symbol +So other symbol +Z separator +Zl line separator +Zp paragraph separator +Zs space separator + +Unicode character class names--scripts: +Adlam +Ahom +Anatolian_Hieroglyphs +Arabic +Armenian +Avestan +Balinese +Bamum +Bassa_Vah +Batak +Bengali +Bhaiksuki +Bopomofo +Brahmi +Braille +Buginese +Buhid +Canadian_Aboriginal +Carian +Caucasian_Albanian +Chakma +Cham +Cherokee +Chorasmian +Common +Coptic +Cuneiform +Cypriot +Cypro_Minoan +Cyrillic +Deseret +Devanagari +Dives_Akuru +Dogra +Duployan +Egyptian_Hieroglyphs +Elbasan +Elymaic +Ethiopic +Georgian +Glagolitic +Gothic +Grantha +Greek +Gujarati +Gunjala_Gondi +Gurmukhi +Han +Hangul +Hanifi_Rohingya +Hanunoo +Hatran +Hebrew +Hiragana +Imperial_Aramaic +Inherited +Inscriptional_Pahlavi +Inscriptional_Parthian +Javanese +Kaithi +Kannada +Katakana +Kawi +Kayah_Li +Kharoshthi +Khitan_Small_Script +Khmer +Khojki +Khudawadi +Lao +Latin +Lepcha +Limbu +Linear_A +Linear_B +Lisu +Lycian +Lydian +Mahajani +Makasar +Malayalam +Mandaic +Manichaean +Marchen +Masaram_Gondi +Medefaidrin +Meetei_Mayek +Mende_Kikakui +Meroitic_Cursive +Meroitic_Hieroglyphs +Miao +Modi +Mongolian +Mro +Multani +Myanmar +Nabataean +Nag_Mundari +Nandinagari +New_Tai_Lue +Newa +Nko +Nushu +Nyiakeng_Puachue_Hmong +Ogham +Ol_Chiki +Old_Hungarian +Old_Italic +Old_North_Arabian +Old_Permic +Old_Persian +Old_Sogdian +Old_South_Arabian +Old_Turkic +Old_Uyghur +Oriya +Osage +Osmanya +Pahawh_Hmong +Palmyrene +Pau_Cin_Hau +Phags_Pa +Phoenician +Psalter_Pahlavi +Rejang +Runic +Samaritan +Saurashtra +Sharada +Shavian +Siddham +SignWriting +Sinhala +Sogdian +Sora_Sompeng +Soyombo +Sundanese +Syloti_Nagri +Syriac +Tagalog +Tagbanwa +Tai_Le +Tai_Tham +Tai_Viet +Takri +Tamil +Tangsa +Tangut +Telugu +Thaana +Thai +Tibetan +Tifinagh +Tirhuta +Toto +Ugaritic +Vai +Vithkuqi +Wancho +Warang_Citi +Yezidi +Yi +Zanabazar_Square + +Vim character classes: +\i identifier character NOT SUPPORTED vim +\I «\i» except digits NOT SUPPORTED vim +\k keyword character NOT SUPPORTED vim +\K «\k» except digits NOT SUPPORTED vim +\f file name character NOT SUPPORTED vim +\F «\f» except digits NOT SUPPORTED vim +\p printable character NOT SUPPORTED vim +\P «\p» except digits NOT SUPPORTED vim +\s whitespace character (== [ \t]) NOT SUPPORTED vim +\S non-white space character (== [^ \t]) NOT SUPPORTED vim +\d digits (== [0-9]) vim +\D not «\d» vim +\x hex digits (== [0-9A-Fa-f]) NOT SUPPORTED vim +\X not «\x» NOT SUPPORTED vim +\o octal digits (== [0-7]) NOT SUPPORTED vim +\O not «\o» NOT SUPPORTED vim +\w word character vim +\W not «\w» vim +\h head of word character NOT SUPPORTED vim +\H not «\h» NOT SUPPORTED vim +\a alphabetic NOT SUPPORTED vim +\A not «\a» NOT SUPPORTED vim +\l lowercase NOT SUPPORTED vim +\L not lowercase NOT SUPPORTED vim +\u uppercase NOT SUPPORTED vim +\U not uppercase NOT SUPPORTED vim +\_x «\x» plus newline, for any «x» NOT SUPPORTED vim + +Vim flags: +\c ignore case NOT SUPPORTED vim +\C match case NOT SUPPORTED vim +\m magic NOT SUPPORTED vim +\M nomagic NOT SUPPORTED vim +\v verymagic NOT SUPPORTED vim +\V verynomagic NOT SUPPORTED vim +\Z ignore differences in Unicode combining characters NOT SUPPORTED vim + +Magic: +(?{code}) arbitrary Perl code NOT SUPPORTED perl +(??{code}) postponed arbitrary Perl code NOT SUPPORTED perl +(?n) recursive call to regexp capturing group «n» NOT SUPPORTED +(?+n) recursive call to relative group «+n» NOT SUPPORTED +(?-n) recursive call to relative group «-n» NOT SUPPORTED +(?C) PCRE callout NOT SUPPORTED pcre +(?R) recursive call to entire regexp (== (?0)) NOT SUPPORTED +(?&name) recursive call to named group NOT SUPPORTED +(?P=name) named backreference NOT SUPPORTED +(?P>name) recursive call to named group NOT SUPPORTED +(?(cond)true|false) conditional branch NOT SUPPORTED +(?(cond)true) conditional branch NOT SUPPORTED +(*ACCEPT) make regexps more like Prolog NOT SUPPORTED +(*COMMIT) NOT SUPPORTED +(*F) NOT SUPPORTED +(*FAIL) NOT SUPPORTED +(*MARK) NOT SUPPORTED +(*PRUNE) NOT SUPPORTED +(*SKIP) NOT SUPPORTED +(*THEN) NOT SUPPORTED +(*ANY) set newline convention NOT SUPPORTED +(*ANYCRLF) NOT SUPPORTED +(*CR) NOT SUPPORTED +(*CRLF) NOT SUPPORTED +(*LF) NOT SUPPORTED +(*BSR_ANYCRLF) set \R convention NOT SUPPORTED pcre +(*BSR_UNICODE) NOT SUPPORTED pcre diff --git a/skills/gtm-triggers/references/triggers.md b/skills/gtm-triggers/references/triggers.md new file mode 100644 index 0000000..e973e4b --- /dev/null +++ b/skills/gtm-triggers/references/triggers.md @@ -0,0 +1,1552 @@ +# Google Tag Manager - Triggers + +## Overview + +Triggers define when tags should fire in Google Tag Manager. They are the "when" component of the Tag-Trigger-Variable system that powers GTM implementations. + +**What Triggers Do:** + +- Control when tags execute +- Evaluate conditions based on page events and user interactions +- Filter tag firing using conditions and variables +- Support multiple events types (page views, clicks, form submissions, custom events) + +## Regular Expressions in Triggers (RE2 Format) + +**CRITICAL:** Google Tag Manager uses **RE2 (GoLang regex)** format, NOT standard JavaScript/PCRE regex. + +### RE2 vs Standard Regex + +**NOT Supported in RE2:** + +- ❌ Backreferences: `\1`, `\2`, `\g{name}` +- ❌ Lookahead assertions: `(?=...)`, `(?!...)` +- ❌ Lookbehind assertions: `(?<=...)`, `(?...)` +- ✅ Alternation: `|` +- ✅ Case-insensitive flag: `(?i)` + +### Common RE2 Patterns for Triggers + +**Match Page URLs:** + +```regex +# Exact match +^https://example\.com/page$ + +# Contains specific path +/checkout/ + +# Query parameter present +\?utm_source= + +# Multiple domains +^https://(www\.)?example\.(com|net) + +# Product pages (ends with digits) +/product/\d+$ + +# Any subdomain +^https://[^.]+\.example\.com/ +``` + +**Match Click URLs:** + +```regex +# PDF downloads +\.pdf$ + +# External links (not your domain) +^https?://(?!example\.com) + +# Telephone links +^tel: + +# Email links +^mailto: + +# File downloads +\.(pdf|docx?|xlsx?|zip)$ +``` + +**Match Form IDs and Classes:** + +```regex +# Form ID starts with "contact" +^contact + +# Form class contains "newsletter" +newsletter + +# Multiple form IDs +^(contact-form|signup-form|subscribe)$ +``` + +**Case-Insensitive Matching:** + +```regex +# Case-insensitive flag at start +(?i)^/checkout + +# Matches: /checkout, /Checkout, /CHECKOUT +(?i)\.pdf$ + +# Matches: .pdf, .PDF, .Pdf +``` + +**Path Matching:** + +```regex +# Blog posts with year/month/day structure +^/blog/\d{4}/\d{2}/\d{2}/ + +# Category pages with optional subcategory +^/category/[\w-]+(/[\w-]+)?$ + +# User profile pages +^/user/[^/]+$ + +# API endpoints +^/api/v\d+/ +``` + +**See Also:** + +- [Best Practices - Regular Expressions in GTM](./best-practices.md#regular-expressions-in-google-tag-manager-re2-format) for RE2 guidelines +- Complete RE2 syntax: `.claude/skills/gtm-core/gtm-core/references/google-rew-regular-expressions-syntax.txt` + +## Trigger Basics + +### How Triggers Work + +1. **Event occurs** - User action or page state change +2. **Trigger evaluates** - GTM checks trigger conditions +3. **Conditions match** - All filters evaluate to true +4. **Tag fires** - Associated tag executes + +### Trigger Components + +- **Trigger Type** - Defines the event (Page View, Click, Custom Event, etc.) +- **Trigger Conditions** - Filters that must match for trigger to fire +- **Operator** - How to compare values (equals, contains, matches regex, etc.) +- **Value** - The expected value to match against + +### Trigger Evaluation + +Triggers evaluate on each GTM event: + +``` +Event: gtm.dom + → Check all triggers for event type "DOM Ready" + → Evaluate each trigger's conditions + → Fire matching tags +``` + +### Multiple Conditions (AND Logic) + +When a trigger has multiple conditions, ALL must be true: + +``` +Condition 1: Page Path contains /checkout +AND +Condition 2: Referrer contains google.com +AND +Condition 3: Cookie - user_type - equals - premium + +All three conditions must match for trigger to fire +``` + +### Some/All Pages or Elements + +Many triggers offer "Some" or "All" options: + +**All Pages:** +- Fires on every page +- No additional conditions + +**Some Pages:** +- Fires only when conditions match +- Requires at least one filter condition + +**Example:** + +``` +Trigger Type: Page View +This trigger fires on: Some Page Views + Page Path contains /products/ + AND + Page Hostname equals www.example.com +``` + +## Trigger Types + +### Pageview Triggers + +#### Page View + +Fires when GTM container loads on a page. + +**Event:** `gtm.js` + +**Common Use Cases:** + +- Fire tags on all pages +- Track specific page views +- Initialize analytics configuration + +**Configuration:** + +``` +Trigger Type: Page View +This trigger fires on: All Pages +``` + +or + +``` +Trigger Type: Page View +This trigger fires on: Some Page Views + Page Path contains /products/ +``` + +**Examples:** + +``` +Name: Pageview - All Pages +Type: Page View +Fires on: All Pages +Use: Fire GA4 Config tag everywhere +``` + +``` +Name: Pageview - Thank You Pages +Type: Page View +Fires on: Some Page Views + Page Path matches RegEx: /(thank-you|confirmation|success) +Use: Fire conversion tags +``` + +``` +Name: Pageview - Product Category +Type: Page View +Fires on: Some Page Views + Page Path starts with /category/ + AND Page Path does not contain /admin/ +Use: Track category page views +``` + +#### DOM Ready + +Fires when the DOM (Document Object Model) is fully constructed but before all resources load. + +**Event:** `gtm.dom` + +**Timing:** After DOM built, before images/stylesheets fully loaded + +**Common Use Cases:** + +- Access DOM elements before page fully renders +- Set up event listeners +- Modify page content early +- Better performance than Window Loaded for tags that don't need full page load + +**Configuration:** + +``` +Trigger Type: DOM Ready +This trigger fires on: All Pages +``` + +**Example:** + +``` +Name: DOM Ready - All Pages +Type: DOM Ready +Fires on: All Pages +Use: Initialize chat widget before page fully loads +``` + +**Timeline:** + +``` +1. GTM Container Loads → Page View (gtm.js) +2. DOM Constructed → DOM Ready (gtm.dom) +3. All Resources Loaded → Window Loaded (gtm.load) +``` + +#### Window Loaded + +Fires when the page is fully loaded, including all resources (images, scripts, stylesheets). + +**Event:** `gtm.load` + +**Timing:** After all page resources completely loaded + +**Common Use Cases:** + +- Tags that need complete page context +- Performance measurements (load time) +- Tags that shouldn't impact page speed +- Third-party widgets that depend on full page load + +**Configuration:** + +``` +Trigger Type: Window Loaded +This trigger fires on: All Pages +``` + +**Example:** + +``` +Name: Window Loaded - Home Page +Type: Window Loaded +Fires on: Some Page Views + Page Path equals / +Use: Fire non-critical tags after page fully loads +``` + +**When to Use Each Pageview Trigger:** + +| Trigger Type | Use When | Performance Impact | +|-------------|----------|-------------------| +| Page View | Need tag to fire immediately | Low | +| DOM Ready | Need DOM access, don't need resources | Medium | +| Window Loaded | Need full page context, non-critical tags | High (delayed) | + +### Click Triggers + +#### All Elements + +Fires when user clicks any element on the page. + +**Event:** `gtm.click` + +**Common Use Cases:** + +- Track button clicks +- Track link clicks +- Track custom element interactions +- Track clicks on dynamically generated elements + +**Built-in Click Variables:** + +- `{{Click Element}}` - The clicked DOM element +- `{{Click Classes}}` - CSS classes of clicked element +- `{{Click ID}}` - ID attribute of clicked element +- `{{Click Target}}` - Target attribute value +- `{{Click URL}}` - href attribute (for links) +- `{{Click Text}}` - Text content of clicked element + +**Configuration:** + +``` +Trigger Type: All Elements +This trigger fires on: Some Clicks + Click Text equals Sign Up +``` + +**Examples:** + +``` +Name: Click - CTA Buttons +Type: All Elements +Fires on: Some Clicks + Click Classes contains cta-button +Use: Track call-to-action button clicks +``` + +``` +Name: Click - Add to Cart +Type: All Elements +Fires on: Some Clicks + Click ID equals add-to-cart-btn +Use: Track add to cart events +``` + +``` +Name: Click - Outbound Links +Type: All Elements +Fires on: Some Clicks + Click URL does not contain example.com + AND Click URL matches RegEx: ^https?:// +Use: Track external link clicks +``` + +``` +Name: Click - Download PDFs +Type: All Elements +Fires on: Some Clicks + Click URL matches RegEx: \.pdf$ +Use: Track PDF downloads +``` + +**Enable Click Variables:** + +Variables → Configure Built-In Variables → Clicks + +#### Just Links + +Fires only when user clicks links (`` tags with `href` attribute). + +**Event:** `gtm.linkClick` + +**Optimized for:** Link tracking with automatic wait time for tag to fire before navigation + +**Wait for Tags:** GTM waits up to 2 seconds for tags to fire before following link + +**Common Use Cases:** + +- Track navigation links +- Track external links +- Track file downloads +- Outbound link tracking + +**Configuration:** + +``` +Trigger Type: Just Links +This trigger fires on: Some Link Clicks + Click URL contains example.com +Wait for Tags: 2000 milliseconds +Check Validation: false +``` + +**Examples:** + +``` +Name: Click - External Links +Type: Just Links +Fires on: Some Link Clicks + Click URL does not contain {{Page Hostname}} + AND Click URL matches RegEx: ^https?:// +Use: Track clicks to external websites +``` + +``` +Name: Click - Email Links +Type: Just Links +Fires on: Some Link Clicks + Click URL starts with mailto: +Use: Track email link clicks +``` + +``` +Name: Click - Phone Links +Type: Just Links +Fires on: Some Link Clicks + Click URL starts with tel: +Use: Track phone number clicks +``` + +``` +Name: Click - File Downloads +Type: Just Links +Fires on: Some Link Clicks + Click URL matches RegEx: \.(pdf|docx?|xlsx?|zip|csv)$ +Use: Track file download clicks +``` + +**Settings:** + +- **Wait for Tags:** Max time (ms) to wait before following link (default: 2000ms) +- **Check Validation:** Wait for form validation before firing (default: false) +- **Enable when:** Additional conditions for trigger to be active + +**All Elements vs Just Links:** + +| Feature | All Elements | Just Links | +|---------|-------------|------------| +| Fires on | Any element | Only `` tags | +| Wait for tags | No | Yes (prevents navigation) | +| Performance | Lower (all clicks) | Better (links only) | +| Use for | Buttons, divs, any element | Navigation, downloads | + +### User Engagement Triggers + +#### Element Visibility + +Fires when a specific element becomes visible in the viewport. + +**Event:** `gtm.elementVisibility` + +**Common Use Cases:** + +- Track ad impressions +- Track scroll-to-content +- Lazy load tracking +- Measure content engagement + +**Configuration:** + +``` +Trigger Type: Element Visibility +Selection Method: ID / CSS Selector +Element ID: featured-product + OR +Element Selector: .product-card + +When to fire this trigger: + ☑ Once per page + ☐ Once per element + ☐ Every time an element appears on screen + +Minimum Percent Visible: 50% +Minimum On-Screen Duration: 1000 milliseconds + +This trigger fires on: All Pages +``` + +**Examples:** + +``` +Name: Visibility - Hero Banner +Type: Element Visibility +Element ID: hero-banner +Fires when: Once per page +Percent Visible: 50% +On-Screen Duration: 2000ms +Use: Track hero banner impressions +``` + +``` +Name: Visibility - Product Cards +Type: Element Visibility +Element Selector: .product-card +Fires when: Once per element +Percent Visible: 75% +On-Screen Duration: 500ms +Fires on: Some Pages + Page Path contains /products/ +Use: Track which products users view +``` + +**Advanced Options:** + +- **Observe DOM changes** - Track dynamically added elements +- **Advanced matching** - Multiple selectors using CSS + +**Built-in Variables:** + +Enable Element Visibility variables: +- `{{Percent Visible}}` - Percentage of element visible +- `{{On-Screen Duration}}` - How long visible (ms) + +#### Form Submission + +Fires when a user submits a form. + +**Event:** `gtm.formSubmit` + +**Common Use Cases:** + +- Track form submissions +- Track lead generation +- Track newsletter signups +- Fire conversion tags + +**Configuration:** + +``` +Trigger Type: Form Submission +This trigger fires on: All Forms + OR +This trigger fires on: Some Forms + Form ID equals contact-form + +Enable when: + (optional conditions) + +Wait for Tags: 2000 milliseconds +Check Validation: True +``` + +**Examples:** + +``` +Name: Form Submit - Contact Form +Type: Form Submission +Fires on: Some Forms + Form ID equals contact-form + OR Form Classes contains contact-form +Check Validation: True +Use: Track contact form submissions +``` + +``` +Name: Form Submit - Newsletter Signup +Type: Form Submission +Fires on: Some Forms + Form ID matches RegEx: (newsletter|subscribe) +Use: Track newsletter signups +``` + +``` +Name: Form Submit - All Forms +Type: Form Submission +Fires on: All Forms +Check Validation: True +Except: + Form ID equals search-form (handled separately) +Use: Track all form submissions except search +``` + +**Settings:** + +- **Wait for Tags:** Time to wait for tags before form submits (default: 2000ms) +- **Check Validation:** Only fire if form passes HTML5 validation (recommended: true) + +**Built-in Form Variables:** + +Enable in Variables → Configure: +- `{{Form ID}}` - Form's ID attribute +- `{{Form Classes}}` - Form's CSS classes +- `{{Form Target}}` - Form's target attribute +- `{{Form URL}}` - Form's action URL +- `{{Form Text}}` - Text content within form + +**Important:** Never capture form field values containing PII (emails, names, phone numbers). + +#### Scroll Depth + +Fires when user scrolls to specific depths on a page. + +**Event:** `gtm.scrollDepth` + +**Common Use Cases:** + +- Measure content engagement +- Track how far users scroll +- Identify drop-off points +- Understand content consumption + +**Configuration:** + +``` +Trigger Type: Scroll Depth + +Vertical Scroll Depths: + Percentages: 25, 50, 75, 90, 100 + OR + Pixels: 500, 1000, 2000 + +This trigger fires on: All Pages + OR +This trigger fires on: Some Pages + Page Path contains /blog/ +``` + +**Examples:** + +``` +Name: Scroll Depth - Blog Posts +Type: Scroll Depth +Percentages: 25, 50, 75, 100 +Fires on: Some Pages + Page Path starts with /blog/ +Use: Measure blog post engagement +``` + +``` +Name: Scroll Depth - Home Page +Type: Scroll Depth +Percentages: 10, 25, 50, 75, 90 +Fires on: Some Pages + Page Path equals / +Use: Track homepage scroll behavior +``` + +``` +Name: Scroll Depth - Long Form Content +Type: Scroll Depth +Pixels: 1000, 2000, 3000, 4000 +Fires on: Some Pages + Page Path contains /guides/ +Use: Track reading depth on long guides +``` + +**Built-in Scroll Variables:** + +Enable Scroll Depth variables: +- `{{Scroll Depth Threshold}}` - The threshold that was reached (e.g., "75") +- `{{Scroll Depth Units}}` - "percent" or "pixels" +- `{{Scroll Direction}}` - "vertical" or "horizontal" + +**Best Practices:** + +- Don't track too many thresholds (max 5-7) +- Use percentages for responsive design +- Consider page length when setting thresholds +- Combine with time on page for engagement metrics + +#### YouTube Video + +Fires when users interact with embedded YouTube videos. + +**Event:** `gtm.video` + +**Requires:** YouTube video embedded via iframe + +**Common Use Cases:** + +- Track video starts +- Track video completion +- Measure video engagement +- Track video progress + +**Configuration:** + +``` +Trigger Type: YouTube Video +Capture: Start, Complete, Pause, Seeking, Buffering, Progress + +Add JavaScript API support to all HTML5 videos: ☑ + +Progress Threshold (%): 10, 25, 50, 75, 90 + +This trigger fires on: Some Videos + Video URL contains product-demo +``` + +**Examples:** + +``` +Name: Video - All Interactions +Type: YouTube Video +Capture: + ☑ Start + ☑ Complete + ☑ Pause + ☑ Progress +Progress: 25, 50, 75 +Fires on: All Videos +Use: Track all video engagement +``` + +``` +Name: Video - Product Demo Complete +Type: YouTube Video +Capture: + ☑ Complete +Fires on: Some Videos + Video URL contains product-demo + OR Video Title contains Product Demo +Use: Track product demo completion +``` + +**Built-in Video Variables:** + +Enable Video variables: +- `{{Video Status}}` - start, pause, complete, progress, etc. +- `{{Video URL}}` - YouTube video URL +- `{{Video Title}}` - Video title from YouTube +- `{{Video Duration}}` - Total video length (seconds) +- `{{Video Current Time}}` - Playback position (seconds) +- `{{Video Percent}}` - Percentage watched +- `{{Video Provider}}` - "youtube" +- `{{Video Visible}}` - true/false if video in viewport + +**Supported Video Events:** + +- **Start** - Video playback begins +- **Complete** - Video finishes +- **Pause** - User pauses video +- **Seeking** - User scrubs to different time +- **Buffering** - Video buffering +- **Progress** - Reaches progress thresholds + +### Other Triggers + +#### Custom Event + +Fires when a specific custom event is pushed to the data layer. + +**Event:** Custom event name from `dataLayer.push()` + +**Common Use Cases:** + +- Track business events (purchase, signup, login) +- Track SPA (Single Page App) page views +- Track custom user interactions +- Integrate with custom application logic + +**Configuration:** + +``` +Trigger Type: Custom Event +Event name: purchase + +Use regex matching: ☐ + +This trigger fires on: All Custom Events + OR +This trigger fires on: Some Custom Events + {{Transaction Value}} greater than 100 +``` + +**Data Layer Push:** + +```javascript +// Push custom event to data layer +dataLayer.push({ + 'event': 'purchase', + 'transactionId': 'T12345', + 'transactionTotal': 99.99, + 'transactionProducts': [ + { + 'sku': 'PROD-001', + 'name': 'Product Name', + 'price': 99.99, + 'quantity': 1 + } + ] +}); +``` + +**Examples:** + +``` +Name: Custom Event - purchase +Type: Custom Event +Event name: purchase +Fires on: All Custom Events +Use: Fire conversion tags on purchase +``` + +``` +Name: Custom Event - High Value Purchase +Type: Custom Event +Event name: purchase +Fires on: Some Custom Events + {{DLV - Transaction Total}} greater than 500 +Use: Track high-value purchases separately +``` + +``` +Name: Custom Event - Virtual Pageview +Type: Custom Event +Event name: virtualPageview +Use: Track SPA navigation +``` + +``` +Name: Custom Event - User Login +Type: Custom Event +Event name: user_login +Fires on: Some Custom Events + {{DLV - User Type}} equals premium +Use: Track premium user logins +``` + +**Regex Matching:** + +``` +Event name: (purchase|transaction|order) +Use regex matching: ☑ + +Matches: purchase, transaction, or order events +``` + +**Best Practices:** + +- Use consistent, descriptive event names +- Use snake_case naming: `user_signup`, `add_to_cart` +- Document all custom events +- Push event first in data layer object + +#### Timer + +Fires at specified time intervals. + +**Event:** `gtm.timer` + +**Common Use Cases:** + +- Track engaged time on page +- Send heartbeat events +- Track video watch time +- Measure time to conversion + +**Configuration:** + +``` +Trigger Type: Timer +Event Name: engagement_timer (custom name for this timer) +Interval: 30000 milliseconds (30 seconds) +Limit: 10 times + +This trigger fires on: All Pages + OR +This trigger fires on: Some Pages + Page Path contains /article/ +``` + +**Examples:** + +``` +Name: Timer - Engagement Heartbeat +Type: Timer +Event Name: engagement_timer +Interval: 30000 (30 seconds) +Limit: 20 +Fires on: All Pages +Use: Track engaged time on site +``` + +``` +Name: Timer - Article Reading Time +Type: Timer +Event Name: article_timer +Interval: 15000 (15 seconds) +Limit: 40 +Fires on: Some Pages + Page Path starts with /blog/ + OR Page Path starts with /articles/ +Use: Measure article reading time +``` + +**Built-in Timer Variables:** + +- `{{Timer Interval}}` - Configured interval (ms) +- `{{Event}}` - Will equal the Event Name you configured + +**Calculating Elapsed Time:** + +Combine with Custom JavaScript variable: + +```javascript +function() { + var interval = {{Timer Interval}}; + var limit = 20; // Your configured limit + + // Calculate elapsed time + return interval * limit / 1000; // Returns seconds +} +``` + +**Settings:** + +- **Event Name** - Custom name to identify this timer +- **Interval** - Time between fires (milliseconds) +- **Limit** - Max number of times to fire (blank = unlimited) + +#### History Change + +Fires when the URL changes without a full page reload (Single Page Applications). + +**Event:** `gtm.historyChange` + +**Common Use Cases:** + +- Track SPA (Single Page App) navigation +- Track AJAX page transitions +- Track URL hash changes +- Track pushState/replaceState events + +**Configuration:** + +``` +Trigger Type: History Change + +This trigger fires on: All History Changes + OR +This trigger fires on: Some History Changes + Page Path contains /app/ +``` + +**Examples:** + +``` +Name: History Change - SPA Navigation +Type: History Change +Fires on: All History Changes +Use: Track virtual pageviews in React/Vue/Angular apps +``` + +``` +Name: History Change - App Section +Type: History Change +Fires on: Some History Changes + Page Path starts with /app/ + AND Page Path does not contain /app/admin/ +Use: Track navigation within app section +``` + +**When URLs Change:** + +The trigger fires when: +- `history.pushState()` is called +- `history.replaceState()` is called +- Hash changes (`#section`) +- Back/forward browser navigation (within SPA) + +**Common Implementation:** + +```javascript +// In your SPA, after route change: +dataLayer.push({ + 'event': 'virtualPageview', + 'pagePath': '/new-page', + 'pageTitle': 'New Page Title' +}); +``` + +**Built-in Variables:** + +Standard page variables update automatically: +- `{{Page URL}}` +- `{{Page Path}}` +- `{{Page Hostname}}` + +#### JavaScript Error + +Fires when JavaScript errors occur on the page. + +**Event:** `gtm.jsError` + +**Common Use Cases:** + +- Track JavaScript errors +- Monitor site health +- Debug issues in production +- Send errors to error tracking tools + +**Configuration:** + +``` +Trigger Type: JavaScript Error + +This trigger fires on: All Errors + OR +This trigger fires on: Some Errors + Error Message does not contain "Non-critical warning" +``` + +**Examples:** + +``` +Name: JavaScript Error - All Errors +Type: JavaScript Error +Fires on: All Errors +Use: Track all JavaScript errors +``` + +``` +Name: JavaScript Error - Critical Only +Type: JavaScript Error +Fires on: Some Errors + Error Message does not contain "Warning" + AND Error URL equals {{Page URL}} +Use: Track only critical errors from own code +``` + +**Built-in Error Variables:** + +Enable Error variables: +- `{{Error Message}}` - The error message text +- `{{Error URL}}` - URL where error occurred +- `{{Error Line}}` - Line number in source file +- `{{Debug Mode}}` - true/false if GTM in debug mode + +**Example Error Tracking Tag:** + +``` +Tag: GA4 - Event - JavaScript Error +Event Name: javascript_error +Event Parameters: + error_message: {{Error Message}} + error_url: {{Error URL}} + error_line: {{Error Line}} + page_url: {{Page URL}} +Trigger: JavaScript Error - All Errors +``` + +**Filtering Noise:** + +Common errors to exclude: + +``` +Error Message does not contain "ResizeObserver loop limit exceeded" +Error Message does not contain "Script error." +Error Message does not contain "Non-Error promise rejection captured" +Error URL equals {{Page URL}} (exclude third-party script errors) +``` + +## Trigger Configuration + +### Trigger Conditions and Filters + +Triggers use conditions to determine when to fire. Conditions compare a variable to a value using an operator. + +**Condition Structure:** + +``` +[Variable] [Operator] [Value] +``` + +**Example:** + +``` +{{Page Path}} contains /checkout +``` + +### Filter Operators + +#### String Operators + +**equals** +- Exact match (case-sensitive) +- Example: `{{Click ID}} equals submit-btn` + +**does not equal** +- Not an exact match +- Example: `{{Page Hostname}} does not equal localhost` + +**contains** +- Value contains substring +- Example: `{{Page URL}} contains /product/` + +**does not contain** +- Value doesn't contain substring +- Example: `{{Click URL}} does not contain example.com` + +**starts with** +- Value begins with string +- Example: `{{Page Path}} starts with /blog/` + +**ends with** +- Value ends with string +- Example: `{{Click URL}} ends with .pdf` + +**matches RegEx** +- Value matches RE2 regular expression +- Example: `{{Page Path}} matches RegEx ^/category/\d+$` +- **IMPORTANT:** Uses RE2 format (see section above) + +**matches RegEx (ignore case)** +- Case-insensitive regex match +- Example: `{{Click URL}} matches RegEx (ignore case) \.pdf$` +- Matches: .pdf, .PDF, .Pdf + +#### Numeric Operators + +**equals** +- Numeric equality +- Example: `{{DLV - Cart Items}} equals 3` + +**does not equal** +- Numeric inequality +- Example: `{{Random Number}} does not equal 0` + +**less than** +- Value < number +- Example: `{{DLV - Transaction Value}} less than 50` + +**less than or equal to** +- Value <= number +- Example: `{{Scroll Depth Threshold}} less than or equal to 50` + +**greater than** +- Value > number +- Example: `{{DLV - Product Price}} greater than 100` + +**greater than or equal to** +- Value >= number +- Example: `{{Video Percent}} greater than or equal to 75` + +#### Boolean Operators + +**is true** +- Variable evaluates to true +- Example: `{{Cookie - user_logged_in}} is true` + +**is false** +- Variable evaluates to false +- Example: `{{Cookie - consent_given}} is false` + +#### Existence Operators + +**is defined** +- Variable has a value (not undefined/null) +- Example: `{{DLV - User ID}} is defined` + +**is not defined** +- Variable is undefined/null/empty +- Example: `{{Cookie - returning_visitor}} is not defined` + +### Using RE2 Regex in Triggers + +Regular expressions enable powerful pattern matching in triggers. + +**URL Matching Examples:** + +``` +# Match checkout pages +{{Page Path}} matches RegEx: ^/checkout + +# Match product pages with SKU +{{Page Path}} matches RegEx: ^/product/[A-Z0-9-]+$ + +# Match blog posts with date structure +{{Page URL}} matches RegEx: /blog/\d{4}/\d{2}/\d{2}/ + +# Match any subdomain +{{Page Hostname}} matches RegEx: ^[^.]+\.example\.com$ + +# Match query parameter presence +{{Page URL}} matches RegEx: [?&]utm_source= + +# Match multiple page types +{{Page Path}} matches RegEx: ^/(checkout|cart|payment) +``` + +**Click URL Matching:** + +``` +# External links (not your domain) +{{Click URL}} matches RegEx: ^https?://(?!example\.com) + +# File downloads +{{Click URL}} matches RegEx: \.(pdf|docx?|xlsx?|zip|csv)(\?|$) + +# Email links +{{Click URL}} matches RegEx: ^mailto:[\w.+-]+@[\w.-]+\.\w{2,}$ + +# Phone links +{{Click URL}} matches RegEx: ^tel:\+?[\d\s\-()]+$ + +# Anchor links (same page) +{{Click URL}} matches RegEx: ^#[^/] +``` + +**Element ID/Class Matching:** + +``` +# Button IDs starting with "btn-" +{{Click ID}} matches RegEx: ^btn- + +# Classes containing "cta" or "call-to-action" +{{Click Classes}} matches RegEx: (cta|call-to-action) + +# Form IDs matching pattern +{{Form ID}} matches RegEx: ^(contact|signup|newsletter)-form$ +``` + +**Text Content Matching:** + +``` +# Case-insensitive "buy now" or "purchase" +{{Click Text}} matches RegEx (ignore case): (buy now|purchase) + +# Numbers in click text +{{Click Text}} matches RegEx: \d+ + +# Specific patterns +{{Click Text}} matches RegEx: ^(Sign Up|Subscribe|Join)$ +``` + +**Common Regex Patterns:** + +```regex +# Match digits +\d+ + +# Match word characters +\w+ + +# Match optional whitespace +\s* + +# Match URL path segment +/[\w-]+ + +# Match query parameter +[?&]param=([^&]+) + +# Match file extension +\.(ext1|ext2|ext3)$ + +# Case-insensitive +(?i)pattern + +# Start of string +^pattern + +# End of string +pattern$ + +# Optional group +(pattern)? + +# Multiple occurrences +pattern{2,5} +``` + +### Multiple Conditions (AND/OR Logic) + +**AND Logic (within a trigger):** + +All conditions must match: + +``` +Trigger: Pageview - Product Category Pages +Type: Page View +Fires on: Some Page Views + {{Page Path}} starts with /category/ + AND {{Page Path}} does not contain /admin/ + AND {{Cookie - user_type}} equals customer +``` + +All three conditions must be true. + +**OR Logic (multiple triggers):** + +Create separate triggers for OR logic: + +``` +Tag: GA4 - Event - Conversion + +Firing Triggers: + - Custom Event - purchase + - Custom Event - transaction + - Custom Event - order_complete + +Tag fires if ANY trigger matches (OR logic) +``` + +**Complex Logic:** + +Combine AND within triggers, OR across triggers: + +``` +Trigger 1: High Value Purchase + {{Custom Event}} equals purchase + AND {{DLV - Transaction Value}} greater than 500 + +Trigger 2: Premium User Purchase + {{Custom Event}} equals purchase + AND {{DLV - User Type}} equals premium + +Tag fires on: (Trigger 1) OR (Trigger 2) +``` + +### Built-in Variables for Triggers + +Enable built-in variables in Variables → Configure: + +**Page Variables:** +- `{{Page URL}}` - Full page URL +- `{{Page Hostname}}` - example.com +- `{{Page Path}}` - /category/products +- `{{Referrer}}` - Previous page URL + +**Click Variables:** +- `{{Click Element}}` - Clicked DOM element +- `{{Click Classes}}` - Element CSS classes +- `{{Click ID}}` - Element ID +- `{{Click URL}}` - Link href +- `{{Click Text}}` - Element text content + +**Form Variables:** +- `{{Form Element}}` - Form DOM element +- `{{Form Classes}}` - Form CSS classes +- `{{Form ID}}` - Form ID +- `{{Form URL}}` - Form action URL +- `{{Form Text}}` - Form text content + +**Utility Variables:** +- `{{Container ID}}` - GTM container ID +- `{{Random Number}}` - Random 0-2147483647 +- `{{Environment Name}}` - Live/Preview/Latest + +## Advanced Trigger Concepts + +### Trigger Groups + +Combine multiple triggers to create complex firing logic. + +**Trigger Group:** + +Create a trigger group in GTM interface to: +- Require ALL triggers in group (AND logic across triggers) +- Simplify complex conditions + +**Use Case:** + +Fire tag only when user is logged in AND on checkout page AND cart value > $50 + +### Trigger Firing Limits + +Some triggers support limits: + +**Timer Trigger:** +- Limit: Maximum number of times to fire + +**Element Visibility:** +- Once per page +- Once per element +- Every time element appears + +### Auto-Event Variables + +Many triggers create auto-event variables: + +**Click Triggers:** +- Automatically populate Click variables + +**Form Triggers:** +- Automatically populate Form variables + +**Video Triggers:** +- Automatically populate Video variables + +**Scroll Triggers:** +- Automatically populate Scroll variables + +Enable these in Variables → Configure Built-In Variables. + +### Event Callbacks + +Execute code after tag fires using `eventCallback`: + +```javascript +dataLayer.push({ + 'event': 'purchase', + 'transactionId': 'T12345', + 'eventCallback': function() { + // Execute after tags fire + console.log('Purchase tags fired'); + // Redirect user + window.location.href = '/thank-you'; + }, + 'eventTimeout': 2000 +}); +``` + +## Best Practices + +### Trigger Naming Conventions + +Use consistent, descriptive naming: + +``` +[Type] - [Condition] - [Description] +``` + +**Examples:** + +- `Pageview - All Pages` +- `Pageview - Product Pages` +- `Click - CTA Buttons` +- `Click - External Links` +- `Custom Event - purchase` +- `Form Submit - Contact Form` +- `Scroll Depth - Blog Posts` +- `Timer - Engagement Tracking` + +### Testing Triggers + +**Preview Mode:** + +1. Enable Preview in GTM +2. Navigate to test page +3. Perform action (click, scroll, etc.) +4. Check "Tags Fired" in debug panel +5. Verify trigger conditions in debug panel + +**Debug Checklist:** + +- [ ] Trigger fires at correct time +- [ ] All conditions evaluate correctly +- [ ] Variables populate with expected values +- [ ] No false positives (unwanted firing) +- [ ] No false negatives (should fire but doesn't) + +### Debugging Trigger Issues + +**Trigger Not Firing:** + +1. Check trigger conditions - are they too restrictive? +2. Verify built-in variables are enabled +3. Check data layer for custom events +4. Review trigger type matches event +5. Check for blocking triggers on tag + +**Trigger Firing Too Often:** + +1. Add more specific conditions +2. Use "Once per page/element" for visibility triggers +3. Add filters to exclude unwanted pages +4. Check for duplicate triggers + +**Variables Not Populating:** + +1. Enable built-in variables in Variables section +2. Check variable names match exactly (case-sensitive) +3. Verify data layer structure for DLV +4. Check timing - is data available when trigger fires? + +### Performance Considerations + +**Minimize Click Listeners:** + +- Use "Just Links" for link tracking (more efficient than "All Elements") +- Add specific conditions to reduce evaluation overhead +- Don't track all clicks indiscriminately + +**Optimize Scroll Depth:** + +- Limit scroll thresholds (5-7 maximum) +- Only enable on relevant pages +- Use percentages for responsive design + +**Timer Triggers:** + +- Use reasonable intervals (not < 5 seconds) +- Set limits to prevent infinite firing +- Only enable on necessary pages + +**Element Visibility:** + +- Use specific selectors (ID preferred over class) +- Enable "Observe DOM changes" only when needed +- Limit to relevant pages + +## Resources + +### Official Documentation + +- [GTM Triggers Overview](https://support.google.com/tagmanager/topic/7679108) +- [Trigger Types Reference](https://support.google.com/tagmanager/answer/7679319) +- [Auto-Event Variables](https://support.google.com/tagmanager/answer/7679316) +- [RE2 Syntax Reference](https://github.com/google/re2/wiki/Syntax) + +### Related GTM Skills + +- [GTM Tags](./tags.md) - Comprehensive tag documentation +- [GTM Variables](./variables.md) - Variable types and configuration +- [GTM Data Layer](../../gtm-datalayer/gtm-datalayer/references/datalayer-fundamentals.md) - Data layer implementation +- [GTM Best Practices](./best-practices.md) - Naming, organization, regex usage + +### Tools + +- [Regex101](https://regex101.com/) - Test regex patterns (select "Golang" for RE2) +- [GTM Preview Mode](https://support.google.com/tagmanager/answer/6107056) +- [Google Tag Assistant](https://tagassistant.google.com/) + +### Community + +- [GTM Community Forum](https://support.google.com/tagmanager/community) +- [Simo Ahava's Blog](https://www.simoahava.com/) +- [Analytics Mania](https://www.analyticsmania.com/) +- [MeasureSchool](https://measureschool.com/) diff --git a/skills/gtm-variables/SKILL.md b/skills/gtm-variables/SKILL.md new file mode 100644 index 0000000..95113a4 --- /dev/null +++ b/skills/gtm-variables/SKILL.md @@ -0,0 +1,427 @@ +--- +name: gtm-variables +description: Expert guidance for configuring Google Tag Manager variables including built-in variables (Page URL, Page Path, Referrer, Click variables, Form variables), data layer variables, custom JavaScript variables, first-party cookies, lookup tables, regex tables, constant variables, URL variables, DOM element variables, auto-event variables, container variables, and user-defined variables. Use when creating variables, configuring data layer variables, writing custom JavaScript variables, parsing URLs, reading cookies, setting up lookup tables, extracting values from DOM elements, debugging variable values, or working with variable formatting and transformations. +--- + +# GTM Variables Configuration + +## Overview +Expert guidance for configuring all types of variables in Google Tag Manager that capture, store, and reuse dynamic values across tags and triggers. + +## When to Use This Skill +Invoke this skill when: +- Creating or configuring GTM variables +- Setting up data layer variables +- Writing custom JavaScript variables +- Working with built-in variables +- Parsing URL parameters or components +- Reading first-party cookies +- Creating lookup tables or regex tables +- Extracting values from DOM elements +- Setting up constant values +- Debugging variable values in Preview mode +- Formatting or transforming variable data +- Optimizing variable performance + +## Built-in Variables + +Built-in variables are pre-configured by GTM. Enable them in Variables → Configure: + +### Page Variables +- **Page URL** - Full URL of current page +- **Page Hostname** - Domain name +- **Page Path** - Path portion of URL (after domain) +- **Referrer** - Previous page URL +- **Random Number** - Random number for cache busting + +### Click Variables +(Enable when using click triggers) +- **Click Element** - Clicked DOM element +- **Click Classes** - CSS classes of clicked element +- **Click ID** - ID attribute of clicked element +- **Click Target** - Target attribute of clicked link +- **Click URL** - href of clicked link +- **Click Text** - Text content of clicked element + +### Form Variables +(Enable when using form triggers) +- **Form Element** - Submitted form DOM element +- **Form Classes** - CSS classes of form +- **Form ID** - ID attribute of form +- **Form Target** - Target attribute of form +- **Form URL** - Action URL of form +- **Form Text** - Text content of form + +### Error Variables +(Enable for JavaScript error tracking) +- **Error Message** - JavaScript error message +- **Error URL** - URL where error occurred +- **Error Line** - Line number of error +- **Debug Mode** - Whether Preview mode is active + +### Video Variables +(Enable for YouTube video tracking) +- **Video Provider** - Video platform (YouTube) +- **Video Status** - Play state (start, pause, complete) +- **Video URL** - URL of video +- **Video Title** - Title of video +- **Video Duration** - Length of video +- **Video Current Time** - Playback position +- **Video Percent** - Percentage watched + +### Scroll Variables +- **Scroll Depth Threshold** - Percentage depth reached +- **Scroll Depth Units** - Pixels or percentage +- **Scroll Direction** - Vertical or horizontal + +### Container Variables +- **Container ID** - GTM container ID +- **Container Version** - Published container version +- **Environment Name** - Environment (Live, Preview, etc.) + +### History Variables +- **New History Fragment** - New URL fragment after history change +- **Old History Fragment** - Previous URL fragment +- **New History State** - New history state object +- **Old History State** - Previous history state + +## User-Defined Variables + +Create custom variables in Variables → New: + +### Data Layer Variable +Most common variable type for accessing data layer values. + +**Configuration:** +- **Data Layer Variable Name**: Path to value in data layer + - Use dot notation: `ecommerce.purchase.transaction_id` + - Use bracket notation for arrays: `products.0.name` +- **Data Layer Version**: Usually "Version 2" +- **Default Value**: Fallback if variable not found + +**Example:** +```javascript +// Data Layer: +dataLayer.push({ + 'event': 'purchase', + 'userId': '12345', + 'ecommerce': { + 'transaction_id': 'T_12345', + 'value': 99.99 + } +}); + +// Variables: +userId → "12345" +ecommerce.transaction_id → "T_12345" +ecommerce.value → 99.99 +``` + +### Custom JavaScript Variable +Execute JavaScript to return dynamic values. + +**Best Practices:** +- Keep code simple and fast +- Always return a value +- Use for complex logic not possible with other variable types +- Avoid DOM manipulation (read-only) +- Test thoroughly + +**Example:** +```javascript +function() { + // Get current date + return new Date().toISOString(); +} +``` + +```javascript +function() { + // Get query parameter + var urlParams = new URLSearchParams(window.location.search); + return urlParams.get('utm_campaign') || 'direct'; +} +``` + +```javascript +function() { + // Format price + var price = {{DL - Price}}; // Reference another variable + return '$' + parseFloat(price).toFixed(2); +} +``` + +### First-Party Cookie +Read cookie values from the browser. + +**Configuration:** +- **Cookie Name**: Name of cookie to read +- **URL Decode**: Decode URI-encoded values +- **Default Value**: Return if cookie not found + +**Example:** +- Cookie Name: `_ga` → Read Google Analytics cookie +- Cookie Name: `user_consent` → Read custom consent cookie + +### Constant +Store static values to reuse across tags. + +**Use Cases:** +- GA4 Measurement ID +- Google Ads Conversion ID +- API keys (if not sensitive) +- Common parameter values + +**Example:** +- Constant: `G-XXXXXXXXXX` (GA4 Measurement ID) +- Constant: `AW-123456789` (Google Ads ID) + +### URL Variable +Parse components from current page URL or custom URL. + +**Component Types:** +- Full URL +- Protocol +- Hostname +- Port +- Path +- Query +- Fragment +- Specific Query Parameter + +**Example:** +URL: `https://example.com/products?category=shoes&sale=true#top` + +- Component: Hostname → `example.com` +- Component: Path → `/products` +- Component: Query → `category=shoes&sale=true` +- Component: Query Key `category` → `shoes` +- Component: Fragment → `top` + +### Lookup Table +Map input values to output values (like a switch statement). + +**Configuration:** +- **Input Variable**: Variable to check +- **Lookup Table**: Input → Output mappings +- **Default Value**: Return if no match + +**Example:** +Input: {{Page Path}} +| Input | Output | +|-------|--------| +| / | homepage | +| /products | product_listing | +| /checkout | checkout | +Default: other + +### RegEx Table +Similar to Lookup Table but uses Regular Expressions for pattern matching. + +**Configuration:** +- **Input Variable**: Variable to check +- **Pattern Table**: RegEx patterns → Output values +- **Default Value**: Return if no match + +**Example:** +Input: {{Page Path}} +| Pattern | Output | +|---------|--------| +| ^/$ | homepage | +| ^/products/.* | product_page | +| ^/blog/.* | blog_post | +| ^/checkout | checkout | +Default: other + +### DOM Element +Extract values from page elements using CSS selectors. + +**Configuration:** +- **Selection Method**: CSS Selector or Element ID +- **Selector**: CSS selector or ID +- **Attribute Name**: Which attribute to get (blank = text content) + +**Examples:** +- Selector: `h1` → Get first H1 text +- Selector: `#product-price`, Attribute: `data-price` → Get price from data attribute +- Selector: `.user-email` → Get email element text + +### Auto-Event Variable +Access properties of auto-events (clicks, forms, etc.). + +**Variable Type**: Select specific auto-event property +- Click Element, Click Classes, Click ID, etc. +- Form Element, Form Classes, Form ID, etc. + +These are usually just enabled as built-in variables instead. + +### Custom Event Variable +Access event parameters from enhanced measurement or custom events. + +**Configuration:** +- **Event Parameter**: Name of parameter to capture +- Works with GA4 events + +### Google Sheets Variable +(Available in GTM web containers) +Fetch values from Google Sheets. + +**Use Cases:** +- Dynamic lookup tables +- A/B test configurations +- Feature flags + +### HTTP Referrer +Get the referrer URL (same as built-in Referrer variable, but user-defined for custom formatting). + +### JavaScript Variable +Access global JavaScript variables. + +**Configuration:** +- **Global Variable Name**: Name of window variable +- Use dot notation for nested properties + +**Example:** +- Global Variable Name: `dataLayer` → Access data layer array +- Global Variable Name: `user.email` → Access window.user.email + +## Common Workflows + +### Create Data Layer Variable +1. Variables → New → Data Layer Variable +2. Set Data Layer Variable Name (e.g., `userId`) +3. Set Default Value (optional) +4. Save with clear name: `DL - User ID` +5. Test in Preview mode → Variables tab + +### Create Custom JavaScript Variable +1. Variables → New → Custom JavaScript +2. Write function that returns value: + ```javascript + function() { + return /* your logic */; + } + ``` +3. Test return value in Preview mode +4. Save with clear name: `CJS - Description` + +### Create Lookup Table +1. Variables → New → Lookup Table +2. Select Input Variable +3. Add Input → Output mappings +4. Set Default Value +5. Test with various inputs in Preview mode +6. Save with clear name: `LUT - Description` + +### Parse URL Parameter +1. Variables → New → URL +2. Component Type: Query +3. Query Key: parameter name (e.g., `utm_source`) +4. Default Value: `(direct)` or other fallback +5. Save as: `URL - UTM Source` + +### Read Cookie Value +1. Variables → New → 1st Party Cookie +2. Cookie Name: cookie to read +3. Enable URL Decode if needed +4. Set Default Value if cookie might not exist +5. Save as: `Cookie - Name` + +## Best Practices + +### Variable Naming +Use consistent prefix format: `[Type] - [Description]` + +Examples: +- `DL - User ID` (Data Layer) +- `CJS - Format Price` (Custom JavaScript) +- `URL - UTM Campaign` (URL) +- `Cookie - Session ID` (First-Party Cookie) +- `Constant - GA4 ID` (Constant) +- `LUT - Page Type` (Lookup Table) + +### Performance Optimization +- Prefer built-in variables over custom JavaScript +- Prefer data layer variables over DOM scraping +- Keep custom JavaScript simple and fast +- Avoid DOM queries in custom JavaScript when possible +- Cache complex calculations in data layer instead +- Remove unused variables regularly + +### Common Pitfalls +- **Undefined variables**: Check spelling, data layer structure, timing +- **Empty values**: Set meaningful default values +- **Variable not updating**: Ensure data layer push happens before variable read +- **Performance issues**: Avoid complex DOM queries in custom JS variables + +### Data Layer Best Practices +- Push data to data layer before GTM fires +- Use consistent data layer structure +- Document data layer schema +- Validate data layer in Preview mode +- See **gtm-datalayer** skill for advanced patterns + +## References +- **references/variables.md** - Comprehensive variable configuration guide with all variable types and advanced patterns +- **references/google-rew-regular-expressions-syntax.txt** - RegEx syntax for regex tables + +Search reference files for specific topics: +```bash +grep -r "Data Layer Variable" references/ +grep -r "Custom JavaScript" references/ +grep -r "Lookup Table" references/ +``` + +## Integration with Other Skills +- **gtm-tags** - Use variables in tag configuration +- **gtm-triggers** - Use variables in trigger conditions +- **gtm-datalayer** - Implement data layer for data layer variables +- **gtm-debugging** - Debug variable values in Preview mode +- **gtm-setup** - Enable built-in variables +- **gtm-best-practices** - Variable naming conventions and optimization + +## Quick Reference + +### Variable Type Selection Guide +- **From data layer**: Data Layer Variable +- **From URL**: URL Variable +- **From cookie**: 1st Party Cookie +- **From page element**: DOM Element Variable +- **Static value**: Constant +- **Complex logic**: Custom JavaScript +- **Value mapping**: Lookup Table or RegEx Table +- **Auto-event data**: Built-in Variables (enable them) + +### Common Custom JavaScript Patterns +```javascript +// Get current timestamp +function() { + return Date.now(); +} + +// Get query parameter +function() { + var params = new URLSearchParams(window.location.search); + return params.get('campaign') || 'none'; +} + +// Format currency +function() { + var value = {{DL - Price}}; + return '$' + parseFloat(value).toFixed(2); +} + +// Get array length +function() { + var items = {{DL - Cart Items}} || []; + return items.length; +} +``` + +### Debugging Checklist +- [ ] Variable name matches data layer path exactly +- [ ] Data layer push happens before variable is read +- [ ] Default value is set for optional variables +- [ ] Built-in variables are enabled +- [ ] Testing in Preview mode → Variables tab +- [ ] Checking variable value at right event diff --git a/skills/gtm-variables/references/google-rew-regular-expressions-syntax.txt b/skills/gtm-variables/references/google-rew-regular-expressions-syntax.txt new file mode 100644 index 0000000..44749b6 --- /dev/null +++ b/skills/gtm-variables/references/google-rew-regular-expressions-syntax.txt @@ -0,0 +1,462 @@ +RE2 regular expression syntax reference +-------------------------­-------­----- + +Single characters: +. any character, possibly including newline (s=true) +[xyz] character class +[^xyz] negated character class +\d Perl character class +\D negated Perl character class +[[:alpha:]] ASCII character class +[[:^alpha:]] negated ASCII character class +\pN Unicode character class (one-letter name) +\p{Greek} Unicode character class +\PN negated Unicode character class (one-letter name) +\P{Greek} negated Unicode character class + +Composites: +xy «x» followed by «y» +x|y «x» or «y» (prefer «x») + +Repetitions: +x* zero or more «x», prefer more +x+ one or more «x», prefer more +x? zero or one «x», prefer one +x{n,m} «n» or «n»+1 or ... or «m» «x», prefer more +x{n,} «n» or more «x», prefer more +x{n} exactly «n» «x» +x*? zero or more «x», prefer fewer +x+? one or more «x», prefer fewer +x?? zero or one «x», prefer zero +x{n,m}? «n» or «n»+1 or ... or «m» «x», prefer fewer +x{n,}? «n» or more «x», prefer fewer +x{n}? exactly «n» «x» +x{} (== x*) NOT SUPPORTED vim +x{-} (== x*?) NOT SUPPORTED vim +x{-n} (== x{n}?) NOT SUPPORTED vim +x= (== x?) NOT SUPPORTED vim + +Implementation restriction: The counting forms «x{n,m}», «x{n,}», and «x{n}» +reject forms that create a minimum or maximum repetition count above 1000. +Unlimited repetitions are not subject to this restriction. + +Possessive repetitions: +x*+ zero or more «x», possessive NOT SUPPORTED +x++ one or more «x», possessive NOT SUPPORTED +x?+ zero or one «x», possessive NOT SUPPORTED +x{n,m}+ «n» or ... or «m» «x», possessive NOT SUPPORTED +x{n,}+ «n» or more «x», possessive NOT SUPPORTED +x{n}+ exactly «n» «x», possessive NOT SUPPORTED + +Grouping: +(re) numbered capturing group (submatch) +(?Pre) named & numbered capturing group (submatch) +(?re) named & numbered capturing group (submatch) +(?'name're) named & numbered capturing group (submatch) NOT SUPPORTED +(?:re) non-capturing group +(?flags) set flags within current group; non-capturing +(?flags:re) set flags during re; non-capturing +(?#text) comment NOT SUPPORTED +(?|x|y|z) branch numbering reset NOT SUPPORTED +(?>re) possessive match of «re» NOT SUPPORTED +re@> possessive match of «re» NOT SUPPORTED vim +%(re) non-capturing group NOT SUPPORTED vim + +Flags: +i case-insensitive (default false) +m multi-line mode: «^» and «$» match begin/end line in addition to begin/end text (default false) +s let «.» match «\n» (default false) +U ungreedy: swap meaning of «x*» and «x*?», «x+» and «x+?», etc (default false) +Flag syntax is «xyz» (set) or «-xyz» (clear) or «xy-z» (set «xy», clear «z»). + +Empty strings: +^ at beginning of text or line («m»=true) +$ at end of text (like «\z» not «\Z») or line («m»=true) +\A at beginning of text +\b at ASCII word boundary («\w» on one side and «\W», «\A», or «\z» on the other) +\B not at ASCII word boundary +\G at beginning of subtext being searched NOT SUPPORTED pcre +\G at end of last match NOT SUPPORTED perl +\Z at end of text, or before newline at end of text NOT SUPPORTED +\z at end of text +(?=re) before text matching «re» NOT SUPPORTED +(?!re) before text not matching «re» NOT SUPPORTED +(?<=re) after text matching «re» NOT SUPPORTED +(? subroutine call NOT SUPPORTED +\g'name' subroutine call NOT SUPPORTED +\k named backreference NOT SUPPORTED +\k'name' named backreference NOT SUPPORTED +\lX lowercase «X» NOT SUPPORTED +\ux uppercase «x» NOT SUPPORTED +\L...\E lowercase text «...» NOT SUPPORTED +\K reset beginning of «$0» NOT SUPPORTED +\N{name} named Unicode character NOT SUPPORTED +\R line break NOT SUPPORTED +\U...\E upper case text «...» NOT SUPPORTED +\X extended Unicode sequence NOT SUPPORTED + +\%d123 decimal character 123 NOT SUPPORTED vim +\%xFF hex character FF NOT SUPPORTED vim +\%o123 octal character 123 NOT SUPPORTED vim +\%u1234 Unicode character 0x1234 NOT SUPPORTED vim +\%U12345678 Unicode character 0x12345678 NOT SUPPORTED vim + +Character class elements: +x single character +A-Z character range (inclusive) +\d Perl character class +[:foo:] ASCII character class «foo» +\p{Foo} Unicode character class «Foo» +\pF Unicode character class «F» (one-letter name) + +Named character classes as character class elements: +[\d] digits (== \d) +[^\d] not digits (== \D) +[\D] not digits (== \D) +[^\D] not not digits (== \d) +[[:name:]] named ASCII class inside character class (== [:name:]) +[^[:name:]] named ASCII class inside negated character class (== [:^name:]) +[\p{Name}] named Unicode property inside character class (== \p{Name}) +[^\p{Name}] named Unicode property inside negated character class (== \P{Name}) + +Perl character classes (all ASCII-only): +\d digits (== [0-9]) +\D not digits (== [^0-9]) +\s whitespace (== [\t\n\f\r ]) +\S not whitespace (== [^\t\n\f\r ]) +\w word characters (== [0-9A-Za-z_]) +\W not word characters (== [^0-9A-Za-z_]) + +\h horizontal space NOT SUPPORTED +\H not horizontal space NOT SUPPORTED +\v vertical space NOT SUPPORTED +\V not vertical space NOT SUPPORTED + +ASCII character classes: +[[:alnum:]] alphanumeric (== [0-9A-Za-z]) +[[:alpha:]] alphabetic (== [A-Za-z]) +[[:ascii:]] ASCII (== [\x00-\x7F]) +[[:blank:]] blank (== [\t ]) +[[:cntrl:]] control (== [\x00-\x1F\x7F]) +[[:digit:]] digits (== [0-9]) +[[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) +[[:lower:]] lower case (== [a-z]) +[[:print:]] printable (== [ -~] == [ [:graph:]]) +[[:punct:]] punctuation (== [!-/:-@[-`{-~]) +[[:space:]] whitespace (== [\t\n\v\f\r ]) +[[:upper:]] upper case (== [A-Z]) +[[:word:]] word characters (== [0-9A-Za-z_]) +[[:xdigit:]] hex digit (== [0-9A-Fa-f]) + +Unicode character class names--general category: +C other +Cc control +Cf format +Cn unassigned code points NOT SUPPORTED +Co private use +Cs surrogate +L letter +LC cased letter NOT SUPPORTED +L& cased letter NOT SUPPORTED +Ll lowercase letter +Lm modifier letter +Lo other letter +Lt titlecase letter +Lu uppercase letter +M mark +Mc spacing mark +Me enclosing mark +Mn non-spacing mark +N number +Nd decimal number +Nl letter number +No other number +P punctuation +Pc connector punctuation +Pd dash punctuation +Pe close punctuation +Pf final punctuation +Pi initial punctuation +Po other punctuation +Ps open punctuation +S symbol +Sc currency symbol +Sk modifier symbol +Sm math symbol +So other symbol +Z separator +Zl line separator +Zp paragraph separator +Zs space separator + +Unicode character class names--scripts: +Adlam +Ahom +Anatolian_Hieroglyphs +Arabic +Armenian +Avestan +Balinese +Bamum +Bassa_Vah +Batak +Bengali +Bhaiksuki +Bopomofo +Brahmi +Braille +Buginese +Buhid +Canadian_Aboriginal +Carian +Caucasian_Albanian +Chakma +Cham +Cherokee +Chorasmian +Common +Coptic +Cuneiform +Cypriot +Cypro_Minoan +Cyrillic +Deseret +Devanagari +Dives_Akuru +Dogra +Duployan +Egyptian_Hieroglyphs +Elbasan +Elymaic +Ethiopic +Georgian +Glagolitic +Gothic +Grantha +Greek +Gujarati +Gunjala_Gondi +Gurmukhi +Han +Hangul +Hanifi_Rohingya +Hanunoo +Hatran +Hebrew +Hiragana +Imperial_Aramaic +Inherited +Inscriptional_Pahlavi +Inscriptional_Parthian +Javanese +Kaithi +Kannada +Katakana +Kawi +Kayah_Li +Kharoshthi +Khitan_Small_Script +Khmer +Khojki +Khudawadi +Lao +Latin +Lepcha +Limbu +Linear_A +Linear_B +Lisu +Lycian +Lydian +Mahajani +Makasar +Malayalam +Mandaic +Manichaean +Marchen +Masaram_Gondi +Medefaidrin +Meetei_Mayek +Mende_Kikakui +Meroitic_Cursive +Meroitic_Hieroglyphs +Miao +Modi +Mongolian +Mro +Multani +Myanmar +Nabataean +Nag_Mundari +Nandinagari +New_Tai_Lue +Newa +Nko +Nushu +Nyiakeng_Puachue_Hmong +Ogham +Ol_Chiki +Old_Hungarian +Old_Italic +Old_North_Arabian +Old_Permic +Old_Persian +Old_Sogdian +Old_South_Arabian +Old_Turkic +Old_Uyghur +Oriya +Osage +Osmanya +Pahawh_Hmong +Palmyrene +Pau_Cin_Hau +Phags_Pa +Phoenician +Psalter_Pahlavi +Rejang +Runic +Samaritan +Saurashtra +Sharada +Shavian +Siddham +SignWriting +Sinhala +Sogdian +Sora_Sompeng +Soyombo +Sundanese +Syloti_Nagri +Syriac +Tagalog +Tagbanwa +Tai_Le +Tai_Tham +Tai_Viet +Takri +Tamil +Tangsa +Tangut +Telugu +Thaana +Thai +Tibetan +Tifinagh +Tirhuta +Toto +Ugaritic +Vai +Vithkuqi +Wancho +Warang_Citi +Yezidi +Yi +Zanabazar_Square + +Vim character classes: +\i identifier character NOT SUPPORTED vim +\I «\i» except digits NOT SUPPORTED vim +\k keyword character NOT SUPPORTED vim +\K «\k» except digits NOT SUPPORTED vim +\f file name character NOT SUPPORTED vim +\F «\f» except digits NOT SUPPORTED vim +\p printable character NOT SUPPORTED vim +\P «\p» except digits NOT SUPPORTED vim +\s whitespace character (== [ \t]) NOT SUPPORTED vim +\S non-white space character (== [^ \t]) NOT SUPPORTED vim +\d digits (== [0-9]) vim +\D not «\d» vim +\x hex digits (== [0-9A-Fa-f]) NOT SUPPORTED vim +\X not «\x» NOT SUPPORTED vim +\o octal digits (== [0-7]) NOT SUPPORTED vim +\O not «\o» NOT SUPPORTED vim +\w word character vim +\W not «\w» vim +\h head of word character NOT SUPPORTED vim +\H not «\h» NOT SUPPORTED vim +\a alphabetic NOT SUPPORTED vim +\A not «\a» NOT SUPPORTED vim +\l lowercase NOT SUPPORTED vim +\L not lowercase NOT SUPPORTED vim +\u uppercase NOT SUPPORTED vim +\U not uppercase NOT SUPPORTED vim +\_x «\x» plus newline, for any «x» NOT SUPPORTED vim + +Vim flags: +\c ignore case NOT SUPPORTED vim +\C match case NOT SUPPORTED vim +\m magic NOT SUPPORTED vim +\M nomagic NOT SUPPORTED vim +\v verymagic NOT SUPPORTED vim +\V verynomagic NOT SUPPORTED vim +\Z ignore differences in Unicode combining characters NOT SUPPORTED vim + +Magic: +(?{code}) arbitrary Perl code NOT SUPPORTED perl +(??{code}) postponed arbitrary Perl code NOT SUPPORTED perl +(?n) recursive call to regexp capturing group «n» NOT SUPPORTED +(?+n) recursive call to relative group «+n» NOT SUPPORTED +(?-n) recursive call to relative group «-n» NOT SUPPORTED +(?C) PCRE callout NOT SUPPORTED pcre +(?R) recursive call to entire regexp (== (?0)) NOT SUPPORTED +(?&name) recursive call to named group NOT SUPPORTED +(?P=name) named backreference NOT SUPPORTED +(?P>name) recursive call to named group NOT SUPPORTED +(?(cond)true|false) conditional branch NOT SUPPORTED +(?(cond)true) conditional branch NOT SUPPORTED +(*ACCEPT) make regexps more like Prolog NOT SUPPORTED +(*COMMIT) NOT SUPPORTED +(*F) NOT SUPPORTED +(*FAIL) NOT SUPPORTED +(*MARK) NOT SUPPORTED +(*PRUNE) NOT SUPPORTED +(*SKIP) NOT SUPPORTED +(*THEN) NOT SUPPORTED +(*ANY) set newline convention NOT SUPPORTED +(*ANYCRLF) NOT SUPPORTED +(*CR) NOT SUPPORTED +(*CRLF) NOT SUPPORTED +(*LF) NOT SUPPORTED +(*BSR_ANYCRLF) set \R convention NOT SUPPORTED pcre +(*BSR_UNICODE) NOT SUPPORTED pcre diff --git a/skills/gtm-variables/references/variables.md b/skills/gtm-variables/references/variables.md new file mode 100644 index 0000000..0076c3b --- /dev/null +++ b/skills/gtm-variables/references/variables.md @@ -0,0 +1,1606 @@ +# Google Tag Manager - Variables + +## Overview + +Variables in Google Tag Manager are placeholders that store and retrieve values. They are the "what" component of the Tag-Trigger-Variable system, providing dynamic data to tags and triggers. + +**What Variables Do:** + +- Store dynamic values (page URL, click text, user data) +- Read from data layer +- Execute JavaScript to compute values +- Retrieve cookie values +- Provide reusable values across tags +- Enable conditional tag firing in triggers + +## Technical Constraints + +### JavaScript Variables (ES5 Requirement) + +**CRITICAL:** Custom JavaScript Variables in GTM require **ECMAScript 5 (ES5)** syntax, NOT modern ES6+. + +**ES6 Features NOT Supported:** + +```javascript +// ❌ WILL FAIL - ES6 syntax +const myVar = 'value'; +let count = 0; +const arrow = () => 'result'; +const template = `Hello ${name}`; +const {property} = object; +const [first, second] = array; + +// ✅ CORRECT - ES5 syntax +var myVar = 'value'; +var count = 0; +var regularFunc = function() { return 'result'; }; +var concatenated = 'Hello ' + name; +var property = object.property; +var first = array[0]; +var second = array[1]; +``` + +**Custom JavaScript variables must:** +- Use `var` for declarations +- Use `function() {}` syntax (not arrow functions) +- Use string concatenation (not template literals) +- Use bracket/dot notation (not destructuring) +- Return a value + +**See Also:** [Best Practices - JavaScript in GTM](./best-practices.md#javascript-in-google-tag-manager-es5-requirement) + +### Regex Table Variables (RE2 Format) + +**CRITICAL:** Regex Table variables use **RE2 (GoLang regex)** format, NOT standard JavaScript regex. + +**NOT Supported in RE2:** +- ❌ Backreferences: `\1`, `\2` +- ❌ Lookahead: `(?=...)` +- ❌ Lookbehind: `(?<=...)` + +**Supported in RE2:** +- ✅ Character classes: `[abc]`, `\d`, `\w` +- ✅ Quantifiers: `*`, `+`, `?`, `{n,m}` +- ✅ Anchors: `^`, `$` +- ✅ Alternation: `|` +- ✅ Groups: `(...)` +- ✅ Case-insensitive: `(?i)` + +**Example Regex Table:** + +``` +Input Variable: {{Page Path}} + +Pattern | Output +---------------------|-------- +^/products/ | product +^/category/ | category +^/blog/\d{4}/ | blog_post +(?i)checkout | checkout (case-insensitive) +``` + +**See Also:** +- [Best Practices - Regular Expressions](./best-practices.md#regular-expressions-in-google-tag-manager-re2-format) +- Complete RE2 syntax: `.claude/skills/gtm-core/gtm-core/references/google-rew-regular-expressions-syntax.txt` + +## Variable Basics + +### How Variables Work + +Variables in GTM are referenced using double curly braces: `{{Variable Name}}` + +**Example:** + +``` +Tag Configuration: + Measurement ID: {{Constant - GA4 Measurement ID}} + User ID: {{DLV - User ID}} + Page URL: {{Page URL}} + +Trigger Configuration: + Condition: {{Page Path}} contains /checkout +``` + +### Variable Types + +GTM has two main categories of variables: + +1. **Built-in Variables** - Predefined variables provided by GTM +2. **User-Defined Variables** - Custom variables you create + +### Variable Resolution + +When a tag fires, GTM resolves all variable references: + +``` +1. Tag fires +2. GTM finds {{Variable Name}} references +3. Variable evaluates and returns value +4. Value replaces {{Variable Name}} in tag +5. Tag sends data to platform +``` + +### Variable Naming Syntax + +Variables are referenced with double curly braces: + +```javascript +{{Variable Name}} +``` + +**Case-Sensitive:** `{{Page URL}}` ≠ `{{page url}}` + +**Spaces Allowed:** `{{DLV - User ID}}` is valid + +**Common Prefixes:** +- `DLV` - Data Layer Variable +- `JS` - Custom JavaScript +- `Constant` - Constant value +- `1P Cookie` - First Party Cookie +- `URL` - URL variable + +## Built-in Variables + +Built-in variables are predefined by GTM and automatically capture common page and event data. + +### Enabling Built-in Variables + +Variables → Configure Built-In Variables → Check boxes to enable + +**Recommended to enable:** +- Page variables (URL, Path, Hostname, Referrer) +- Click variables (Element, ID, Classes, URL, Text) +- Container variables (Container ID, Version) + +### Page Variables + +**{{Page URL}}** +- Full page URL including protocol, domain, path, query +- Example: `https://example.com/products?id=123` + +**{{Page Hostname}}** +- Domain name only +- Example: `example.com` or `www.example.com` + +**{{Page Path}}** +- URL path without domain or query string +- Example: `/products/category/item` + +**{{Referrer}}** +- URL of previous page +- Example: `https://google.com/search?q=example` + +**{{Page Fragment}}** +- URL hash/fragment +- Example: `section-1` from `page.html#section-1` + +### Click Variables + +**{{Click Element}}** +- The DOM element that was clicked +- Returns: Element object + +**{{Click ID}}** +- ID attribute of clicked element +- Example: `submit-button` + +**{{Click Classes}}** +- CSS classes of clicked element +- Example: `btn btn-primary cta` + +**{{Click URL}}** +- href attribute of clicked link +- Example: `https://example.com/page` or `mailto:email@example.com` + +**{{Click Text}}** +- Text content of clicked element +- Example: `Sign Up Now` + +**{{Click Target}}** +- Target attribute of clicked link +- Example: `_blank`, `_self` + +### Form Variables + +**{{Form Element}}** +- The form DOM element +- Returns: Form element object + +**{{Form ID}}** +- ID attribute of form +- Example: `contact-form` + +**{{Form Classes}}** +- CSS classes of form +- Example: `contact-form newsletter-signup` + +**{{Form URL}}** +- Action URL of form +- Example: `/submit-contact` + +**{{Form Text}}** +- Text content within form +- Example: Combined text from labels and buttons + +**Important:** Never capture actual form field values containing PII. + +### Video Variables + +**{{Video Status}}** +- Current video status +- Values: `start`, `pause`, `complete`, `progress`, `seek`, `buffering` + +**{{Video URL}}** +- YouTube video URL +- Example: `https://www.youtube.com/watch?v=VIDEO_ID` + +**{{Video Title}}** +- Video title from YouTube +- Example: `Product Demo Video` + +**{{Video Duration}}** +- Total video length in seconds +- Example: `180` + +**{{Video Current Time}}** +- Current playback position in seconds +- Example: `45` + +**{{Video Percent}}** +- Percentage of video watched +- Example: `25` + +**{{Video Provider}}** +- Video platform +- Example: `youtube` + +**{{Video Visible}}** +- Whether video is in viewport +- Values: `true` or `false` + +### Scroll Variables + +**{{Scroll Depth Threshold}}** +- The scroll threshold reached +- Example: `75` (from 75% threshold) + +**{{Scroll Depth Units}}** +- Unit type of threshold +- Values: `percent` or `pixels` + +**{{Scroll Direction}}** +- Scroll direction +- Values: `vertical` or `horizontal` + +### Visibility Variables + +**{{Percent Visible}}** +- Percentage of element visible in viewport +- Example: `50` + +**{{On-Screen Duration}}** +- Duration element was visible (milliseconds) +- Example: `2000` + +### Error Variables + +**{{Error Message}}** +- JavaScript error message +- Example: `Uncaught TypeError: Cannot read property 'x' of undefined` + +**{{Error URL}}** +- URL where error occurred +- Example: `https://example.com/script.js` + +**{{Error Line}}** +- Line number in source file +- Example: `42` + +### Utility Variables + +**{{Container ID}}** +- GTM container ID +- Example: `GTM-XXXXXX` + +**{{Container Version}}** +- Published container version number +- Example: `12` + +**{{Debug Mode}}** +- Whether GTM is in debug/preview mode +- Values: `true` or `false` + +**{{Random Number}}** +- Random integer 0 to 2147483647 +- Use for: Cache busting, sampling + +**{{Environment Name}}** +- Current GTM environment +- Values: `Live`, `Latest`, `Environment Name` + +**{{HTML ID}}** +- ID attribute of GTM container element +- Example: `gtm-container` + +## User-Defined Variable Types + +Create custom variables to extend GTM functionality. + +### Data Layer Variable + +Reads values from the data layer. + +**Purpose:** +- Access data pushed to data layer +- Read ecommerce data +- Retrieve custom event data +- Get user information + +**Configuration:** + +``` +Variable Type: Data Layer Variable +Data Layer Variable Name: userId + (reads from dataLayer.userId) + +Data Layer Version: Version 2 +Set Default Value: (optional) + Default Value: unknown +``` + +**Data Layer Structure:** + +```javascript +dataLayer.push({ + 'userId': '12345', + 'userType': 'premium', + 'pageCategory': 'product', + 'ecommerce': { + 'currency': 'USD', + 'value': 99.99 + } +}); +``` + +**Variable Examples:** + +``` +Name: DLV - User ID +Data Layer Variable Name: userId +Returns: "12345" + +Name: DLV - User Type +Data Layer Variable Name: userType +Returns: "premium" + +Name: DLV - Currency +Data Layer Variable Name: ecommerce.currency +Returns: "USD" + +Name: DLV - Transaction Value +Data Layer Variable Name: ecommerce.value +Returns: 99.99 +``` + +**Nested Values:** + +Use dot notation for nested objects: + +```javascript +dataLayer.push({ + 'user': { + 'id': '12345', + 'preferences': { + 'newsletter': true + } + } +}); + +// Variable Configuration: +Data Layer Variable Name: user.id +Returns: "12345" + +Data Layer Variable Name: user.preferences.newsletter +Returns: true +``` + +**Array Access:** + +Use array index notation: + +```javascript +dataLayer.push({ + 'products': [ + {'id': 'SKU001', 'name': 'Product 1'}, + {'id': 'SKU002', 'name': 'Product 2'} + ] +}); + +// Variable Configuration: +Data Layer Variable Name: products.0.id +Returns: "SKU001" + +Data Layer Variable Name: products.1.name +Returns: "Product 2" +``` + +**Default Values:** + +Set a fallback value when data layer variable is undefined: + +``` +Variable Type: Data Layer Variable +Data Layer Variable Name: userId +Set Default Value: ✓ + Default Value: not-logged-in +``` + +### Custom JavaScript Variable + +Executes JavaScript code to compute and return a value. + +**IMPORTANT:** Must use ES5 JavaScript syntax (see section above). + +**Purpose:** +- Perform calculations +- Transform data +- Combine multiple variables +- Conditional logic +- Access DOM elements +- Parse strings + +**Configuration:** + +``` +Variable Type: Custom JavaScript +Custom JavaScript: + function() { + // ES5 JavaScript only + var result = 'computed value'; + return result; + } +``` + +**Examples:** + +**Simple Transformation:** + +```javascript +function() { + var pagePath = {{Page Path}}; + return pagePath.toLowerCase(); +} +``` + +**Calculation:** + +```javascript +function() { + var price = {{DLV - Product Price}}; + var quantity = {{DLV - Quantity}}; + return price * quantity; +} +``` + +**Conditional Logic:** + +```javascript +function() { + var userType = {{DLV - User Type}}; + + if (userType === 'premium') { + return 'high-value'; + } else if (userType === 'standard') { + return 'medium-value'; + } else { + return 'low-value'; + } +} +``` + +**String Manipulation:** + +```javascript +function() { + var pageUrl = {{Page URL}}; + + // Extract query parameter + var match = pageUrl.match(/[?&]utm_source=([^&]+)/); + + if (match && match[1]) { + return decodeURIComponent(match[1]); + } + + return 'direct'; +} +``` + +**Array Operations:** + +```javascript +function() { + var products = {{DLV - Products}}; + + if (!products || !products.length) { + return 0; + } + + var total = 0; + for (var i = 0; i < products.length; i++) { + total += products[i].price * products[i].quantity; + } + + return total.toFixed(2); +} +``` + +**Date/Time:** + +```javascript +function() { + var now = new Date(); + + // Format: YYYY-MM-DD + var year = now.getFullYear(); + var month = ('0' + (now.getMonth() + 1)).slice(-2); + var day = ('0' + now.getDate()).slice(-2); + + return year + '-' + month + '-' + day; +} +``` + +**DOM Access:** + +```javascript +function() { + var element = document.querySelector('.product-sku'); + + if (element) { + return element.textContent.trim(); + } + + return 'not-found'; +} +``` + +**Combining Variables:** + +```javascript +function() { + var category = {{DLV - Category}}; + var subcategory = {{DLV - Subcategory}}; + + return category + ' > ' + subcategory; +} +``` + +**Error Handling:** + +```javascript +function() { + try { + var data = {{DLV - Complex Data}}; + return data.property.nestedProperty; + } catch (e) { + return 'error'; + } +} +``` + +**Best Practices:** +- Always return a value +- Handle undefined/null cases +- Use try/catch for risky operations +- Keep code simple and readable +- Test thoroughly in Preview mode +- Use ES5 syntax only + +### First-Party Cookie + +Reads value from a first-party cookie. + +**Purpose:** +- Access cookie values +- Read user preferences +- Get session data +- Retrieve stored identifiers + +**Configuration:** + +``` +Variable Type: First-Party Cookie +Cookie Name: user_id + (case-sensitive, exact cookie name) + +URI-decode cookie: ✓ (recommended) +Format Value: Text / Lowercase / Uppercase / None +``` + +**Examples:** + +``` +Name: 1P Cookie - User ID +Cookie Name: user_id +Returns: Value of user_id cookie + +Name: 1P Cookie - Session ID +Cookie Name: _session_id +Returns: Session identifier + +Name: 1P Cookie - Consent +Cookie Name: cookie_consent +Returns: "accepted" or "denied" +``` + +**Cookie Path and Domain:** + +GTM reads cookies accessible to the current page based on: +- Cookie domain (`.example.com` vs `www.example.com`) +- Cookie path (`/` vs `/products/`) + +**URI Decoding:** + +Enable "URI-decode cookie" to decode URL-encoded values: + +``` +Cookie Value: hello%20world +With URI-decode: hello world +Without URI-decode: hello%20world +``` + +**Use Cases:** + +```javascript +// Check if user is logged in +Trigger Condition: {{1P Cookie - user_logged_in}} equals true + +// Segment by user type +Tag Parameter: user_segment = {{1P Cookie - user_segment}} + +// Exclude internal traffic +Blocking Trigger: {{1P Cookie - internal_user}} equals true +``` + +### JavaScript Variable + +Reads value from a global JavaScript variable. + +**Purpose:** +- Access window-level variables +- Read values from other scripts +- Get application state +- Access third-party SDKs + +**Configuration:** + +``` +Variable Type: JavaScript Variable +Global Variable Name: myGlobalVar + (reads from window.myGlobalVar) +``` + +**Examples:** + +``` +Name: JS Var - Google Analytics ID +Global Variable Name: ga.q +Returns: GA queue + +Name: JS Var - App Version +Global Variable Name: app.version +Returns: Application version number + +Name: JS Var - User Object +Global Variable Name: currentUser +Returns: User object from application +``` + +**Nested Properties:** + +Access nested object properties: + +```javascript +// Global object: +window.appConfig = { + environment: 'production', + features: { + chatEnabled: true + } +}; + +// Variable Configuration: +Global Variable Name: appConfig.environment +Returns: "production" + +Global Variable Name: appConfig.features.chatEnabled +Returns: true +``` + +**Difference from Custom JavaScript:** + +| Feature | JavaScript Variable | Custom JavaScript | +|---------|---------------------|-------------------| +| Purpose | Read existing global variable | Execute code to compute value | +| Syntax | Variable name only | Full function | +| Code execution | No | Yes | +| Use when | Variable already exists | Need to process/calculate | + +### URL Variable + +Extracts components from a URL. + +**Purpose:** +- Parse URL components +- Extract query parameters +- Get specific URL parts +- Clean URLs for reporting + +**Configuration:** + +``` +Variable Type: URL +Component Type: URL / Protocol / Hostname / Path / Query / Fragment / Port +Query Key: (if Component Type = Query) +``` + +**Component Types:** + +**URL** - Full URL +``` +Input: https://example.com/products?id=123#reviews +Output: https://example.com/products?id=123#reviews +``` + +**Protocol** +``` +Input: https://example.com/page +Output: https: +``` + +**Hostname** +``` +Input: https://www.example.com/page +Output: www.example.com +``` + +**Port** +``` +Input: https://example.com:8080/page +Output: 8080 +``` + +**Path** +``` +Input: https://example.com/products/category +Output: /products/category +``` + +**Query** +``` +Input: https://example.com/page?utm_source=google&utm_medium=cpc +Component: Query +Output: utm_source=google&utm_medium=cpc +``` + +**Query (Specific Key)** +``` +Input: https://example.com/page?utm_source=google&id=123 +Component: Query +Query Key: utm_source +Output: google +``` + +**Fragment** +``` +Input: https://example.com/page#section-2 +Output: section-2 +``` + +**Examples:** + +``` +Name: URL - UTM Source +Component Type: Query +Query Key: utm_source +Returns: Value of utm_source parameter + +Name: URL - Product ID +Component Type: Query +Query Key: product_id +Returns: Product ID from query string + +Name: URL - Current Path +Component Type: Path +Returns: /category/products + +Name: URL - Referrer Hostname +Component Type: Hostname +Returns: Hostname of referrer +``` + +### Regex Table Variable + +Maps input values to output values using regular expressions. + +**IMPORTANT:** Uses RE2 regex format (see section above). + +**Purpose:** +- Pattern matching and transformation +- Map URLs to page types +- Categorize pages +- Clean/normalize data + +**Configuration:** + +``` +Variable Type: Regex Table +Input Variable: {{Page Path}} +Set Default Value: (optional) + Default Value: other + +Pattern | Output +-----------------|-------- +^/products/ | product +^/category/ | category +^/blog/ | blog +^/$ | home +``` + +**Pattern Matching:** + +Patterns are evaluated in order, first match wins. + +**Examples:** + +**Page Type Classification:** + +``` +Input Variable: {{Page Path}} + +Pattern | Output +-------------------------|------------ +^/$ | home +^/products/[^/]+$ | product_detail +^/products/ | product_list +^/category/ | category +^/checkout/ | checkout +^/cart | cart +^/blog/\d{4}/ | blog_post +^/about | about +``` + +**URL Cleaning:** + +``` +Input Variable: {{Page URL}} + +Pattern | Output +-----------------------------|--------------------------- +\?.*$ | (removes query string) +#.*$ | (removes fragment) +``` + +**Campaign Source Mapping:** + +``` +Input Variable: {{URL - UTM Source}} + +Pattern | Output +-----------------|-------- +^google$ | paid_search +^facebook$ | social +^email | email +^newsletter | email +.* | other +``` + +**RE2 Regex Examples:** + +```regex +# Exact match +^/checkout$ + +# Starts with +^/products/ + +# Contains +blog + +# Ends with +\.pdf$ + +# Digits +\d+ + +# Word characters +[\w-]+ + +# Optional group +(/subcategory)? + +# Multiple options +^/(checkout|cart|payment) + +# Case-insensitive +(?i)pdf +``` + +**Default Value:** + +Set a fallback when no pattern matches: + +``` +Set Default Value: ✓ + Default Value: uncategorized +``` + +### Lookup Table Variable + +Maps exact input values to output values. + +**Purpose:** +- Simple key-value mapping +- Exact match transformation +- No regex needed + +**Configuration:** + +``` +Variable Type: Lookup Table +Input Variable: {{DLV - User Type}} +Set Default Value: (optional) + Default Value: unknown + +Input | Output +-----------|---------- +premium | high +standard | medium +basic | low +trial | trial +``` + +**Examples:** + +**User Tier Mapping:** + +``` +Input Variable: {{DLV - User Type}} + +Input | Output +---------------|------------ +enterprise | tier_1 +business | tier_2 +professional | tier_3 +starter | tier_4 +Default Value: tier_5 +``` + +**Currency Symbol:** + +``` +Input Variable: {{DLV - Currency}} + +Input | Output +-------|-------- +USD | $ +EUR | € +GBP | £ +JPY | ¥ +Default Value: $ +``` + +**Page Type Mapping:** + +``` +Input Variable: {{Page Hostname}} + +Input | Output +-----------------------|-------- +www.example.com | main_site +shop.example.com | shop +blog.example.com | blog +support.example.com | support +Default Value: unknown +``` + +**When to Use:** + +- **Lookup Table** - Exact string matches, simple mapping +- **Regex Table** - Pattern matching, complex rules, RE2 regex + +### Constant Variable + +Stores a fixed value. + +**Purpose:** +- Store IDs and tokens +- Reusable configuration values +- Easy updates across container + +**Configuration:** + +``` +Variable Type: Constant +Value: G-XXXXXXXXXX +``` + +**Examples:** + +``` +Name: Constant - GA4 Measurement ID +Value: G-XXXXXXXXXX +Use: In GA4 Configuration Tag + +Name: Constant - Google Ads Conversion ID +Value: AW-123456789 +Use: In Google Ads tags + +Name: Constant - Environment +Value: production +Use: Conditional logic + +Name: Constant - Tracking Enabled +Value: true +Use: Enable/disable tracking +``` + +**Benefits:** + +- **Single source of truth** - Update once, applies everywhere +- **Easy migration** - Change IDs in one place +- **Clarity** - Descriptive variable names vs hardcoded IDs + +**Example Usage:** + +``` +Instead of: + Measurement ID: G-XXXXXXXXXX (hardcoded in 10 tags) + +Use: + Measurement ID: {{Constant - GA4 Measurement ID}} + (update constant once, affects all tags) +``` + +### Custom Event Variable + +Returns the name of the current event. + +**Purpose:** +- Get event name in tags +- Conditional logic based on event +- Debug event tracking + +**Configuration:** + +``` +Variable Type: Custom Event +(no additional configuration) +``` + +**Returns:** + +Current event name from data layer or GTM: + +```javascript +dataLayer.push({'event': 'purchase'}); +// {{Event}} returns: "purchase" + +// Page load +// {{Event}} returns: "gtm.js" + +// DOM Ready +// {{Event}} returns: "gtm.dom" + +// Click +// {{Event}} returns: "gtm.click" +``` + +**Use Cases:** + +``` +Tag Configuration: + Event Name: {{Event}} + (sends current event name as parameter) + +Custom JavaScript: + var event = {{Event}}; + if (event === 'purchase') { + return 'conversion'; + } + +Trigger Condition: + {{Event}} equals virtualPageview +``` + +### Element Visibility Variable + +Stores visibility information for elements. + +**Purpose:** +- Access visibility metrics +- Track how visible elements are +- Measure engagement + +**Configuration:** + +``` +Variable Type: Element Visibility +(automatically available with Element Visibility trigger) +``` + +**Available Variables:** + +- `{{Percent Visible}}` - Percentage of element visible (0-100) +- `{{On-Screen Duration}}` - Time visible in milliseconds + +**Use with Element Visibility Trigger:** + +``` +Trigger: Element Visibility +Element ID: featured-product +Minimum Percent Visible: 50% + +Tag fires with variables: + {{Percent Visible}} = 75 (example) + {{On-Screen Duration}} = 2500 (example) +``` + +### Auto-Event Variable + +Returns values from auto-event triggers. + +**Purpose:** +- Access event-specific data +- Get click, form, scroll data +- Reference auto-event values + +**Examples:** + +**Click Variables:** +- `{{Click Element}}` +- `{{Click ID}}` +- `{{Click Classes}}` +- `{{Click URL}}` +- `{{Click Text}}` + +**Form Variables:** +- `{{Form Element}}` +- `{{Form ID}}` +- `{{Form Classes}}` +- `{{Form URL}}` + +**Scroll Variables:** +- `{{Scroll Depth Threshold}}` +- `{{Scroll Depth Units}}` + +**Video Variables:** +- `{{Video Status}}` +- `{{Video URL}}` +- `{{Video Percent}}` + +Enable in Variables → Configure Built-In Variables + +### HTTP Referrer Variable + +Extracts components from the referrer URL. + +**Purpose:** +- Parse referrer URL +- Extract referrer domain +- Get referrer query parameters +- Track traffic sources + +**Configuration:** + +``` +Variable Type: HTTP Referrer +Component Type: URL / Protocol / Hostname / Path / Query / Fragment / Port +Query Key: (if Component Type = Query) +``` + +**Examples:** + +``` +Name: HTTP Referrer - Hostname +Component Type: Hostname +Returns: google.com + +Name: HTTP Referrer - Search Query +Component Type: Query +Query Key: q +Returns: Search query from Google +``` + +**Use Cases:** + +``` +Trigger Condition: + {{HTTP Referrer - Hostname}} contains google + (fire on traffic from Google) + +Tag Parameter: + referrer_domain = {{HTTP Referrer - Hostname}} + (send referrer domain to analytics) +``` + +### DOM Element Variable + +Extracts data from DOM elements using CSS selectors. + +**Purpose:** +- Read text from page elements +- Get attribute values +- Access dynamic page content +- Extract structured data + +**Configuration:** + +``` +Variable Type: DOM Element +Selection Method: CSS Selector / ID +Element Selector: .product-price + OR +Element ID: product-sku + +Attribute Name: (optional) + data-product-id + href + src + etc. +``` + +**Examples:** + +**Extract Text Content:** + +``` +Name: DOM - Product Price +Selection Method: CSS Selector +Element Selector: .product-price +Returns: "$99.99" +``` + +**Extract Attribute:** + +``` +Name: DOM - Product SKU +Selection Method: ID +Element ID: product-info +Attribute Name: data-sku +Returns: "PROD-12345" +``` + +**Extract href:** + +``` +Name: DOM - Canonical URL +Selection Method: CSS Selector +Element Selector: link[rel="canonical"] +Attribute Name: href +Returns: Canonical URL +``` + +**Use Cases:** + +```javascript +// Product page tracking +dataLayer.push({ + 'event': 'product_view', + 'productSku': {{DOM - Product SKU}}, + 'productPrice': {{DOM - Product Price}} +}); + +// Dynamic content +Trigger Condition: + {{DOM - Element Text}} contains "In Stock" +``` + +**Important:** +- Element must exist when variable evaluates +- Returns first matching element +- Returns empty if element not found + +## Variable Configuration + +### Creating Variables + +1. **Variables → New** +2. **Click variable configuration area** +3. **Choose variable type** +4. **Configure settings** +5. **Name variable** (use consistent naming convention) +6. **Save** + +### Variable Formatting + +Some variables offer formatting options: + +**Text Transformations:** +- Lowercase +- Uppercase +- No formatting + +**URI Decoding:** +- Decode URI-encoded values +- Useful for cookies and URLs + +**Strip www:** +- Remove "www." from hostnames + +### Variable Default Values + +Set fallback values when variable is undefined: + +``` +Data Layer Variable: + Set Default Value: ✓ + Default Value: not-set +``` + +**When to use:** +- Prevent undefined values in tags +- Ensure clean data +- Avoid empty parameters + +### Variable Version History + +GTM tracks variable changes: + +**View History:** +Variables → Select Variable → Versions tab + +**Compare Versions:** +- See what changed +- Restore previous version +- Audit trail + +## Advanced Variable Concepts + +### Variable Macros + +Variables can reference other variables: + +``` +Custom JavaScript Variable: +function() { + var path = {{Page Path}}; + var category = {{DLV - Category}}; + + return path + ' | ' + category; +} + +Returns: "/products/item | electronics" +``` + +### Nested Variables + +Variables within variables: + +``` +Lookup Table: +Input Variable: {{DLV - User Type}} + +Input | Output +-----------|--------------------------- +premium | {{Constant - Premium ID}} +standard | {{Constant - Standard ID}} +``` + +### Variable Precedence + +When multiple data layer pushes contain the same key: + +```javascript +// Initial push +dataLayer.push({'category': 'electronics'}); + +// Later push +dataLayer.push({'category': 'computers'}); + +// {{DLV - Category}} returns: "computers" (latest value) +``` + +**Data Layer Version 2** merges objects: + +```javascript +dataLayer.push({ + 'user': { + 'id': '123', + 'type': 'premium' + } +}); + +dataLayer.push({ + 'user': { + 'name': 'John' + } +}); + +// Result (merged): +user: { + id: '123', + type: 'premium', + name: 'John' +} +``` + +### Undefined Variable Handling + +When a variable is undefined: + +**In Tags:** +- GTM may send empty string "" +- Or literal "undefined" +- Set default values to control behavior + +**In Triggers:** +- Undefined ≠ empty string +- Use "is defined" / "is not defined" operators +- Check existence before comparing values + +**Best Practice:** + +``` +Trigger Condition: + {{DLV - User ID}} is defined + AND + {{DLV - User ID}} does not equal "" +``` + +### Variable Caching + +GTM caches variable values during tag firing: + +**Within single event:** +- Variable evaluates once +- Same value used across all tags for that event + +**Across events:** +- Variables re-evaluate for each new event + +**Example:** + +```javascript +// Event 1 +dataLayer.push({'event': 'pageview', 'value': 100}); +// {{DLV - Value}} = 100 for all tags firing on this event + +// Event 2 +dataLayer.push({'event': 'click', 'value': 200}); +// {{DLV - Value}} = 200 for all tags firing on this event +``` + +## Best Practices + +### Variable Naming Conventions + +Use consistent, descriptive naming: + +``` +[Type Prefix] - [Purpose] +``` + +**Examples:** + +- `DLV - User ID` (Data Layer Variable) +- `JS - Page Category` (Custom JavaScript) +- `Constant - GA4 Measurement ID` +- `1P Cookie - Session ID` (First Party Cookie) +- `URL - UTM Source` (URL Variable) +- `Regex Table - Page Type` +- `Lookup - User Tier` + +**Type Prefixes:** + +``` +DLV - Data Layer Variable +JS - Custom JavaScript +Constant - Constant +1P Cookie - First Party Cookie +URL - URL Variable +HTTP Ref - HTTP Referrer +DOM - DOM Element +``` + +### When to Use Constants vs Variables + +**Use Constant when:** +- Value never changes (IDs, tokens) +- Same value across all instances +- Easy updates needed + +**Use Data Layer Variable when:** +- Value changes per page/event +- Dynamic user data +- Custom event data + +**Use Custom JavaScript when:** +- Need to compute value +- Transform or combine data +- Complex logic required + +### Performance Optimization + +**Minimize Custom JavaScript:** +- Use built-in variables when possible +- Keep code simple and fast +- Avoid heavy DOM manipulation +- Cache results when appropriate + +**Data Layer Best Practices:** +- Push data before GTM container loads +- Use consistent naming +- Structure data logically +- Don't push PII + +**Variable Efficiency:** +- Reuse constants across container +- Don't create duplicate variables +- Regular cleanup of unused variables + +### Debugging Variables + +**Preview Mode:** + +1. Enable Preview +2. Navigate to page +3. Select event in debug panel +4. Click "Variables" tab +5. See all variable values for that event + +**Console Debugging:** + +```javascript +// Check data layer +console.log(dataLayer); + +// Check specific variable in GTM object +console.log(google_tag_manager['GTM-XXXXXX'].dataLayer.get('variableName')); +``` + +**Common Issues:** + +**Variable is undefined:** +- Data not in data layer yet +- Typo in variable name +- Data layer push after GTM evaluated + +**Variable has wrong value:** +- Data layer structure mismatch +- Timing issue (evaluated too early/late) +- Cached old value + +**Variable not updating:** +- Data layer not pushing new value +- Variable caching within event +- Need to use Custom Event to re-evaluate + +## Resources + +### Official Documentation + +- [GTM Variables Guide](https://support.google.com/tagmanager/topic/7182737) +- [Built-in Variables Reference](https://support.google.com/tagmanager/answer/7182738) +- [Data Layer Variables](https://developers.google.com/tag-platform/tag-manager/web/datalayer) +- [Custom JavaScript Variables](https://support.google.com/tagmanager/answer/7683362) + +### Related GTM Skills + +- [GTM Tags](./tags.md) - Comprehensive tag documentation +- [GTM Triggers](./triggers.md) - Trigger types and configuration +- [GTM Data Layer](../../gtm-datalayer/gtm-datalayer/references/datalayer-fundamentals.md) - Data layer implementation +- [GTM Best Practices](./best-practices.md) - Naming, ES5, RE2 regex + +### Tools + +- [Babel REPL](https://babeljs.io/repl) - ES6 to ES5 transpilation +- [Regex101](https://regex101.com/) - Test regex (select "Golang" for RE2) +- [GTM Preview Mode](https://support.google.com/tagmanager/answer/6107056) +- [Data Layer Inspector Chrome Extension](https://chrome.google.com/webstore/detail/datalayer-checker/ffljdddodmkedhkcjhpmdajhjdbkogke) + +### Community + +- [GTM Community Forum](https://support.google.com/tagmanager/community) +- [Simo Ahava's Blog](https://www.simoahava.com/) +- [Analytics Mania](https://www.analyticsmania.com/) +- [MeasureSchool](https://measureschool.com/)