# I Automated GA4 with YAML. Now It Flags What to Check in SEO.

> A CLI + MCP server that configures GA4 and Search Console from YAML, and flags what to check in your SEO by impact. Deterministic: you make the calls. →

I started building this so I'd stop losing 40 minutes clicking through the GA4 UI every time I spun up a project. What I didn't expect was where it ended up: now the tool doesn't just configure analytics, it **flags** what to look at in my SEO, ranked by how many clicks it's costing me. It doesn't decide for me. It puts the data in front of me, prioritized, and I pick what to touch.

That distinction is the whole point, and I'll come back to it. This is the story of how it happened, what it does now, and what came out when I pointed the full suite at this very blog, including the moment the tool got it wrong (and why that gave me *more* confidence, not less).

## The original problem: clicking like a monkey

I have several side projects. This portfolio, [WealthSim](https://wealthsim.app), and a few smaller ones. Each needs analytics, and every time I spun up a new one the ritual was the same: create the GA4 property (easy), then click. Conversions, ten clicks each. Dimensions, more clicks. Metrics, more. Data retention, where was that option again? Enhanced measurement, another menu.

**Thirty or forty minutes of repetitive clicking.** And the worst part: if I wanted the same config on another project, I'd repeat it all from scratch. GA4 has no "export configuration." No way to copy setup between properties.

After configuring the third project in one week, I thought what you're thinking right now: *this is exactly the kind of work that should be automated.*

## The premise: analytics as code

I define my infra as code. My CI/CD as code. Why not my analytics?

That was the whole idea. A YAML file that describes how I want GA4, and one command to apply it:

```yaml
project:
  name: "My Project"

ga4:
  property_id: "123456789"
  tier: standard

conversions:
  - name: newsletter_signup
    counting_method: ONCE_PER_SESSION

dimensions:
  - parameter: article_category
    scope: EVENT
```

```bash
ga4 setup --config my-project.yaml
```

Boom. Config applied. No clicks, no typos, versioned in git next to your code. From 40 minutes to under one. That was [GA4 Manager](https://github.com/garbarok/ga4-manager) v1.

## The turn: it stopped showing me data and started pointing at what to check

This is where the tool got genuinely interesting.

The first half of its life was **configuration**: apply this YAML, create these conversions, submit this sitemap. Useful, but passive. The second half, the part that actually changed how I work, is **diagnostics**. Today, on v2.3.1, the MCP server exposes 20 tools, and the newest ones don't show you data: they tell you where to start.

The one I use most is **`gsc_opportunities`**. It finds queries where you already rank on page 1-2 (position 5-20) but with a CTR below the median for that bucket, and sorts them by `potential_clicks`: the extra monthly clicks you'd gain just by hitting the median CTR. Biggest wins first.

On this blog? It found exactly one. And at position ~18, page two, where a title tweak won't move it. The tool didn't inflate it: it told me the truth, that there was no gold mine here. On a site with real traffic, this is the tool that buys you coffee; on a small one, it confirms there are no shortcuts. Both answers work for me.

The other three in the new group follow the same "what first" philosophy:

- **`gsc_cannibalization`**: queries where two or more of your pages split impressions and eat each other's authority; it points at the canonical candidate.
- **`gsc_ctr_anomaly`**: (query, page) pairs where position barely moved but CTR collapsed. Translation: your snippet stopped converting. Rewrite it.
- **`gsc_health`**: an indexing report that diffs each URL's state against the prior snapshot, so an accidental `noindex` surfaces in days, not weeks. Silent when everything's green. Built for a cron.

And here's what, for me, separates this from the pile: **none of it is the AI guessing at SEO.** These are deterministic queries against the Search Console API (a Go CLI, structured output). The LLM only orchestrates the calls and summarizes the result; the interpretation and the "yes-this, no-that" are mine. If vibe coding gives you hives (it does me), that's exactly the line that matters: the tool surfaces, you judge.

## What happened when I pointed it at this blog

This blog is one of my side projects and runs on a site that uses GA4 Manager via MCP, so last week I sat down with Claude and pointed the full suite at `oscargallegoruiz.com`. I expected to find disasters. Reality was more boring, and more interesting than it sounds.

43 of 43 pages indexed. `gsc_ctr_anomaly`: empty. `seo_page_audit` on the top pages: clean, zero errors. In short: the site was healthy. And a tool telling you "everything's fine" instead of inventing a problem to justify itself is a signal I trust.

But there was a scare. `gsc_sitemaps_list` returned an *empty* list, as if I'd never submitted the sitemap. The easy reflex would've been to resubmit it and move on. Instead I verified with `gsc_sitemaps_get`… and there it was, submitted and downloaded by Google, no problem. **The empty list was a bug in my own tool, not an SEO failure.**

And that's the moral I care about, the one that ties back to everything above: *I didn't trust the output blindly.* If I'd acted on the false positive, I'd have "fixed" something that wasn't broken. Verifying saved me the embarrassment, and along the way, the tool I wrote to catch problems caught a bug in itself. I fixed it. That's the healthy relationship with a tool like this: it gives you leads, you confirm them. The one I never trust is the one that promises you don't need to confirm anything.

## What I learned building this

**Google has two GA4 APIs.** The Data API (query data) and the Admin API (configure). GA4 Manager uses the Admin one, which is `v1alpha`. Yes, alpha. Some endpoints change without warning, and certain metric types (like CURRENCY) have poorly documented restrictions. You find out the hard way.

**The limits are real and it validates them before touching anything.** On the free tier: 30 conversions, 50 dimensions, 50 metrics. If you're close, it warns you before you eat a 400 from the API.

**Parameter names are forever.** Once you create a dimension or metric, the parameter name is permanently reserved. Even if you archive it. My fix is ugly but works: version the names (`reading_time_v3`).

## Use it from your assistant (MCP)

The piece that changed everything was the MCP server. It exposes the 20 tools to Claude Desktop, Claude CLI, VS Code, Cursor, and Cline, so I can say in plain language:

> "Compare last week's traffic to the prior week, audit the homepage and the worst-ranking post, and tell me what to check."

And Claude orchestrates the calls and stitches the results together. But same principle as always: it hands me a prioritized list, not a `git push`. The decisions stay mine.

And here's the connection that's got me hooked: `gsc_health` is **built for a cron**. Which means it's not just a tool I invoke, but a [loop I can leave running](/en/blog/stop-prompting-write-loops/): an agent that checks indexing every Monday, compares traffic, and only bothers me if something broke. That's the next rung. You stop asking and build the loop that asks for you.

## What it does NOT do (let's be honest)

- **Audiences:** the tool doesn't create them. It generates the docs so you can build them by hand in the UI (the Admin API can create audiences; I just haven't wired that into the tool yet).
- **BigQuery linking:** automated. `ga4 link --service bigquery` creates the export link through the Admin API.
- **Search Console linking:** manual. The tool prints a setup guide; you finish the link in the UI.
- **Historical data:** this configures GA4, it doesn't migrate data. Collection starts after setup.

## Install: one script

```bash
git clone https://github.com/garbarok/ga4-manager.git
cd ga4-manager
./scripts/setup.sh
```

The script verifies prerequisites (`gcloud`, `jq`, `curl`), picks the auth path with you (ADC for personal use, service-account key for CI), enables the required APIs, smoke-tests every endpoint, and prints which manual permissions you still need. It's idempotent: rerun it without fear.

## Quick questions

**Is it free?** Yes, open source under MIT. The code is on [GitHub](https://github.com/garbarok/ga4-manager).

**Do I need to know Go?** No. Download the binary and write YAML.

**Can it delete my data?** No. It only uses the Admin API for configuration; it can't touch your analytics data. Worst case is creating or deleting config resources, which you can always recreate. And `--dry-run` lets you preview everything before applying.

**Do the SEO audits need a paid API key?** Only if you want Core Web Vitals inside `seo_page_audit`. Without a key, PageSpeed Insights rate-limits to one request per ~100s and the audit skips CWV. With a free key you get the normal quota.

**`gsc_analytics_run` vs `gsc_traffic_compare`?** The first is a snapshot of one period (top queries, pages, CTR, position). The second compares two periods and gives you URL-level drops and gains. One to see what you have, the other to see what changed.

If you have a couple of side projects you rarely touch, you probably don't need it. If you run three or more and you're sick of clicking the same UI for the hundredth time: [try it](https://github.com/garbarok/ga4-manager), star it if it helps, and open an issue if something breaks (or if you find another bug like the sitemap-list one).

**Related reading:** why `gsc_health` on a cron is the natural next step: [stop prompting your agent, start writing loops](/en/blog/stop-prompting-write-loops/).

---

**P.S.** If you try it, tell me what it found on [Twitter/X](https://x.com/garbarok). Now if you'll excuse me, I've got a cron to set up.
