{"id":50349225,"url":"https://github.com/kb223/gtm-ga4-sync","last_synced_at":"2026-05-29T20:03:57.572Z","repository":{"id":353212780,"uuid":"1218442069","full_name":"kb223/gtm-ga4-sync","owner":"kb223","description":"Event tagging as code for GTM + GA4. Declare events in YAML, provision triggers, variables, tags, and custom dimensions in one command.","archived":false,"fork":false,"pushed_at":"2026-04-22T23:07:24.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T00:26:33.598Z","etag":null,"topics":["agentic-analytics","analytics","claude-code","datalayer","event-tracking","ga4","google-analytics","google-tag-manager","gtm"],"latest_commit_sha":null,"homepage":"https://kennethjbuchanan.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kb223.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T22:07:50.000Z","updated_at":"2026-04-22T23:07:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kb223/gtm-ga4-sync","commit_stats":null,"previous_names":["kb223/gtm-ga4-sync"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kb223/gtm-ga4-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kb223%2Fgtm-ga4-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kb223%2Fgtm-ga4-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kb223%2Fgtm-ga4-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kb223%2Fgtm-ga4-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kb223","download_url":"https://codeload.github.com/kb223/gtm-ga4-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kb223%2Fgtm-ga4-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33668261,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-analytics","analytics","claude-code","datalayer","event-tracking","ga4","google-analytics","google-tag-manager","gtm"],"created_at":"2026-05-29T20:03:56.838Z","updated_at":"2026-05-29T20:03:57.563Z","avatar_url":"https://github.com/kb223.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gtm-ga4-sync\n\nDeclare your dataLayer events in YAML. One command provisions the matching GTM triggers, variables, and GA4 Event tags — and registers every custom parameter as a custom dimension or metric in GA4.\n\n[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n\n---\n\n## Install\n\nTwo paths. Pick whichever matches how you work.\n\n### Path A — Hand it to Claude Code (or another AI coding agent)\n\n1. **Open** a Claude Code session in any directory you're comfortable cloning into.\n\n2. **Paste this prompt into the session and send it:**\n\n\u003e I'm giving you a skill called gtm-ga4-tagging. Please do the following in order:\n\u003e\n\u003e 1. Clone the repo: `git clone https://github.com/kb223/gtm-ga4-sync`\n\u003e 2. Symlink the skill into my Claude Code skills folder: `ln -s \"$PWD/gtm-ga4-sync/skills/gtm-ga4-tagging\" ~/.claude/skills/gtm-ga4-tagging`\n\u003e 3. Install the CLI: `cd gtm-ga4-sync \u0026\u0026 python3 -m venv .venv \u0026\u0026 .venv/bin/pip install .`\n\u003e 4. Walk me through the one-time Google Cloud OAuth client setup from the README in the repo.\n\u003e 5. Once OAuth is done, help me tag the engagement events on my site end-to-end.\n\nClaude Code will clone, install, prompt you through the Google Cloud steps, and (after auth) help you design an event map and apply it to your GTM + GA4. You stay in chat the whole time — Claude runs the terminal commands itself.\n\n### Path B — Run it yourself in the terminal\n\n**Terminal commands** (no AI agent involved):\n\n```bash\ngit clone https://github.com/kb223/gtm-ga4-sync\ncd gtm-ga4-sync\npython3 -m venv .venv\n.venv/bin/pip install .\n```\n\nThe CLI is now at `.venv/bin/gtm-ga4-sync`. Activate the venv (`source .venv/bin/activate`) or invoke the binary directly.\n\n---\n\n## How you use it after install\n\n### 1. Write an `events.yml` describing your events\n\n```yaml\nevents:\n  virtual_page_view:\n    params: [page_path, page_title]\n  cta_click:\n    params: [cta_name, cta_destination, cta_location]\n  form_submit:\n    params: [form_id, form_name, form_destination]\n\nmetrics:\n  - results_count\n```\n\nSee [`events.example.yml`](./events.example.yml) for a full starter covering page views, CTAs, outbound links, forms, downloads, search, video, and newsletter signups.\n\n### 2. Find your GTM + GA4 IDs\n\n**Terminal command:**\n\n```bash\ngtm-ga4-sync discover\n```\n\nPrints every GTM account/container and GA4 property your authenticated user can see. Copy the IDs you'll use.\n\n### 3. Preview with a dry run\n\n**Terminal command:**\n\n```bash\ngtm-ga4-sync apply \\\n  --config events.yml \\\n  --gtm-account \u003cid\u003e --gtm-container \u003cid\u003e \\\n  --ga4-property \u003cid\u003e \\\n  --dry-run\n```\n\nThe CLI prompts you to pick a **workspace** — never defaults to the Default Workspace. GTM best practice: do every change in a dedicated workspace so you can diff and QA before publishing. Create one in the GTM UI first if you don't have one, or pick an existing one.\n\nIt also auto-detects your **measurement ID** from the existing Google Tag config in the selected workspace. If multiple tags exist, it prompts you to pick. If none exists, it asks you to enter a `G-XXXXXXXXXX` manually. Pass `--measurement-id G-XXXXXXX` to skip the prompt entirely.\n\nDry-run shows every would-be-created, skipped, or reused resource with a summary at the end. No writes hit Google.\n\n### 4. Apply\n\n**Terminal command** (same as above, without `--dry-run`):\n\n```bash\ngtm-ga4-sync apply \\\n  --config events.yml \\\n  --gtm-account \u003cid\u003e --gtm-container \u003cid\u003e \\\n  --ga4-property \u003cid\u003e\n```\n\n### 5. Push to your dataLayer from your app\n\n```javascript\nwindow.dataLayer = window.dataLayer || []\nwindow.dataLayer.push({\n  event: 'cta_click',\n  cta_name: 'view_my_work',\n  cta_destination: '/projects',\n  cta_location: 'home_hero',\n})\n```\n\n### 6. Review + publish\n\nOpen the GTM UI → your workspace → review the new tags/triggers/variables → **Submit** → give the version a name → **Publish**. The tool never publishes for you; review is always a human step.\n\n---\n\n## One-time Google Cloud OAuth setup\n\nNeeded before the first `gtm-ga4-sync apply` or `discover`. The tool uses your own OAuth client in your own GCP project — Google treats it as first-party, so sensitive scopes (`tagmanager.edit.containers`, `analytics.edit`) don't hit the \"unverified app\" block you'd hit with a generic OAuth client.\n\n\u003e Google renamed \"OAuth consent screen\" to **Google Auth Platform → Branding** in 2025/2026. The URLs below use the new paths.\n\n**Terminal command — create the project and enable the APIs:**\n\n```bash\ngcloud projects create my-analytics-ops --name=\"Analytics Ops\"\ngcloud config set project my-analytics-ops\ngcloud services enable tagmanager.googleapis.com analyticsadmin.googleapis.com\n```\n\n**In your browser — configure the consent screen:**\n\nGo to https://console.developers.google.com/auth/branding?project=my-analytics-ops\n\n- App name: anything\n- User support email: yours\n- Audience → User type: **Internal** if on Google Workspace (recommended — skips scope verification), **External** otherwise\n- Developer contact: yours\n- Accept the policy → **Create**. You can skip the scopes screen — the tool requests them at runtime.\n\n**In your browser — create the OAuth client:**\n\nGo to https://console.developers.google.com/auth/clients?project=my-analytics-ops\n\n- **Create Client**\n- Application type: **Desktop app**\n- Name: anything\n- **Create** → **Download JSON** → save to `~/.config/gtm-ga4-sync/client-secret.json`\n\n**Terminal command — first auth:**\n\n```bash\ngtm-ga4-sync auth --client-secret ~/.config/gtm-ga4-sync/client-secret.json\n```\n\nOpens your browser, you approve the scopes, token gets cached at `~/.config/gtm-ga4-sync/token.json`. Subsequent runs reuse it — you don't need `--client-secret` again unless you run with `--force-reauth` or rotate the client.\n\n---\n\n## Commands reference\n\n```\ngtm-ga4-sync auth         Run one-time OAuth consent, cache refresh token\ngtm-ga4-sync discover     List every GTM account/container + GA4 property you can access\ngtm-ga4-sync apply        Provision GTM resources + GA4 dimensions from events.yml\n  --config FILE           events.yml path  (required)\n  --gtm-account ID        (required)\n  --gtm-container ID      (required)\n  --workspace NAME/ID     Target workspace. Omit to pick interactively.\n  --ga4-property ID       (required)\n  --measurement-id G-X    Override auto-detection of the GA4 measurement ID\n  --dry-run               Preview without writing\n  --skip-gtm              Only register GA4 dimensions/metrics\n  --skip-ga4              Only provision GTM resources\n  --force-reauth          Force browser consent even if a token is cached\n```\n\n---\n\n## Duplicate detection (two layers)\n\nSafe to point at containers that were set up manually before:\n\n1. **By name** — if `DLV - cta_name` already exists, skip.\n2. **By function** — if an existing variable already reads the `cta_name` dataLayer key under a different name (e.g. `1PC - CTA Name`), reuse it instead of creating a second one. Works the same for Custom Event triggers (matched by the event name they filter on) and GA4 Event tags (matched by their `eventName`).\n\nDry-run labels each item: `[skip]` for name matches, `[reuse]` for function matches, `[+]` for new creates.\n\n---\n\n## events.yml structure\n\n```yaml\nevents:\n  \u003cevent_name\u003e:\n    params: [\u003cparam1\u003e, \u003cparam2\u003e, ...]\n\nmetrics:               # optional — params to register as GA4 custom metrics (numeric)\n  - \u003cnumeric_param\u003e\n\ndisplay_names:         # optional — defaults to Title Case of param name\n  \u003cparam\u003e: \"Friendly Name\"\n```\n\nParam names follow GA4 rules: alphanumeric + underscore only, avoid reserved names like `source`, `medium`, `campaign`, `currency`, `value`, `items`. Display names: alphanumeric, underscore, or space only (no hyphens or parentheses).\n\n---\n\n## GTM naming conventions\n\nFollows community norms so the container reads consistently:\n\n| Prefix | Resource |\n|---|---|\n| `DLV - ` | Data Layer Variable |\n| `CJS - ` | Custom JavaScript |\n| `CON - ` | Constant |\n| `LT - ` | Lookup Table |\n| `RXT - ` | RegEx Table |\n| `CE - ` | Custom Event Trigger |\n| `PV - ` | Page View Trigger |\n| `Click - ` | Click Trigger |\n| `GA4 - ` | GA4 Event Tag |\n| `HTML - ` | Custom HTML Tag |\n\n---\n\n## Limitations\n\n- Publishes nothing — you review the diff in GTM and hit Submit. Intentional.\n- Doesn't delete resources when you remove events from config. Clean up in the UI.\n- GA4 property + data stream must exist first — doesn't create those.\n- One container per run. Multi-container is a shell loop away if you need it.\n\n## Author\n\nBuilt by [Kenneth J. Buchanan](https://kennethjbuchanan.com).\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkb223%2Fgtm-ga4-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkb223%2Fgtm-ga4-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkb223%2Fgtm-ga4-sync/lists"}