Want a free personalized recommendation for your software stack?Get yours
That Marketing Buddy

How to Set Up Website Analytics From Claude Code

Tracking script, event taxonomy, behavioural events, conversions and revenue, built by describing them to an AI instead of clicking through a tag manager.

Joonas RotkoJoonas RotkoUpdated Aug 23, 20269 min read
Updated regularly
Data from Buddy's database

For ten years my analytics work looked like this. Open Google Tag Manager. Build a container. Wire up triggers and variables. Publish. Check whether the tag fired. Discover it fired twice. Fix it. Then open Looker Studio and spend an afternoon rebuilding a client dashboard that had broken again because a field name changed underneath it.

How to Set Up Website Analytics From Claude Code

That was a real skill and agencies charged real money for it, mine included. It is also almost entirely gone as a job. Not because analytics got simpler, but because the tools started shipping MCP servers, and an MCP server means you can build the whole implementation by describing it instead of clicking it.

This is how I actually do it now. The examples use Rybbit because it has the cleanest setup path of any analytics tool I have looked at, but the shape is the same for PostHog, Matomo, Piwik PRO or anything else with a first-party server. I run Umami and PostHog on this site, and every pattern below is lifted from that build rather than invented for a tutorial.

What you need before you start

An analytics tool with a vendor-built MCP server, Claude Code (or Cursor, Codex, any MCP client), and access to your own site code. That is the whole list. No tag manager, no separate dashboard tool, no BigQuery.

Why an MCP server changes what setup means

A REST API has been available on analytics tools for years and it did not change anything, because using it meant writing a script, handling auth, reading the response schema and building something to display it. That is a project. Most people never started it.

An MCP server collapses that. Your assistant reads the tool list, knows the schema, authenticates once, and can both read your data and write your configuration in the same conversation it is editing your code. The gap between "I wonder if that event is firing" and knowing closes to about fifteen seconds.

If the distinction between an API, an MCP server and a CLI is fuzzy, I wrote a separate breakdown of when each one is the right shape. For this post the short version is enough: MCP is the one your assistant can drive without you writing glue code.

A. Get the tracking ID in, and proxy it while you are there

Add the site in your analytics tool, copy the site ID, drop the script in your head tag. Rybbit gives you this:

html<script
  src="https://app.rybbit.io/api/script.js"
  data-site-id="YOUR_SITE_ID"
  async
></script>

That is the five-minute version and it works. Now the part that took me two rounds to learn on my own setup, so you can skip the first round.

Serve the tracker from your own domain. A meaningful slice of your visitors run an adblocker, and every one of them blocks third-party analytics hostnames on sight regardless of whether the tool is privacy-friendly. Rybbit documents a proxy guide covering Next.js, Nginx and Caddy. On this site the Umami tracker loads from a same-origin path and posts to a route on my own domain, and PostHog goes through a same-origin /ingest rewrite.

That single change is worth more than any dashboard feature.

Proxying buys you a second thing that nobody mentions: the site ID stops being public. On my setup the client-side script ships a placeholder ID and the real one gets injected server-side in the proxy route. Anyone viewing source sees a UUID of all zeroes. Without that, your site ID sits in your HTML for anyone who wants to spam your analytics with fake events.

Ask for this, do not build it

The proxy is exactly the kind of task to hand over. "Read the Rybbit proxy guide, add a same-origin rewrite for the tracker in this Next.js app, keep the site ID in an environment variable and serve a placeholder to the client." That is a ten-minute job for an agent and an afternoon of reading docs for you.

Connect the MCP before you write any tracking code

Do this second, not last. Everything after this step is easier if the assistant can already see your data.

bash# OAuth, no API key to create or store
claude mcp add --transport http rybbit https://app.rybbit.io/api/mcp

# then, inside a Claude Code session:
/mcp   → select rybbit → Authenticate

Your browser opens, you approve, and the connection acts with your own role. No secret in a config file and nothing to rotate. Claude Code, Claude Desktop, Codex and opencode all support this flow. Add --scope user if you want it available across all your projects instead of just this one.

If you would rather hand the agent a narrower credential than your own account, create a scoped API key instead. Rybbit lets you restrict it per resource and action:

json{
  "analytics": ["read"],
  "sessions": ["read"],
  "goals": ["read", "write"]
}

That key can read your traffic and manage goals and cannot touch team membership or delete a site. The MCP tool list itself gets filtered to those scopes, so the assistant never even sees the tools it is not allowed to call. If you are nervous about giving an agent write access to production analytics, this is the control that should settle it.

B. Let the assistant tell you what to track

This is the step people get wrong, and I got it wrong for years in agency work. The old process was a stakeholder workshop that produced a measurement plan nobody read, followed by tagging whatever survived the meeting. The events ended up describing the org chart rather than the site.

Do the opposite now. Point the assistant at the actual site and let it propose the taxonomy, because it can read every route and component and you cannot hold all of them in your head.

The prompt I use

Read this codebase and my sitemap. Then propose a tracking plan for Rybbit: which custom events I should fire, on which pages or components, and what properties each should carry. Prioritise events that would change a decision I make about the site. Flag anything already covered by pageviews so I do not double-track it.

The last two sentences are what make it useful. Without a prioritisation instruction you get forty events, most of them noise. Without the double-tracking instruction you get a custom event that duplicates something the pageview already tells you, and you pay for the volume twice.

On this site that process is what produced the three engagement events I still run, and I would not have picked all three myself. Then you review the list, cut it in half, and let the agent write the instrumentation. In Rybbit that is either a data attribute on the element:

html<button
  data-rybbit-event="newsletter_signup"
  data-rybbit-prop-placement="footer"
>
  Subscribe
</button>

Or the JavaScript call, when the event is not tied to a click:

javascriptwindow.rybbit.event("newsletter_signup", {
  placement: "footer",
  variant: "b",
});

One naming rule saves you a rebuild later. Pick a convention before the first event ships, lowercase with underscores is fine, and tell the assistant to follow it. Event names are the one thing you cannot cleanly rename after the fact, because your history splits into before and after. I have made that mistake and the fix is worse than the discipline.

C. Behavioural analytics: measure reading, not visiting

A pageview tells you a URL loaded. It does not tell you whether a human looked at it. For a content site that gap is the whole game, so this is where I spend most of the instrumentation effort.

Three events cover it, and they are the three running on this site right now:

  • scroll_depth: fires once each at 25, 50, 75 and 100 percent. One event per threshold per pageview, deduped, so a jittery scroll does not inflate it.
  • read_complete: fires when someone passes 50 percent AND has been on the page 30 seconds. Either signal alone is noise. Together they are a decent proxy for actually reading.
  • time_on_page: fires on exit with a bucketed dwell time, under 10 seconds, 10 to 30, 30 to 60, 1 to 3 minutes, over 3. Guarded so it logs once, because beforeunload and pagehide both fire on desktop and you will double-count without it.

Buckets rather than raw milliseconds, because you are never going to ask "what was the median dwell in ms". You are going to ask "what share of readers got past 30 seconds", and a bucket answers that without a query.

The bot problem, which is bigger than you think

Here is the thing that reframed my numbers completely. A large share of what a naive analytics setup calls traffic is not human. Not just declared crawlers, which every tool filters, but referrer-spoofing botnets on residential IPs running real Chrome user agents. They pass a user-agent filter, they pass a datacenter-IP filter, and they land in your dashboard as visitors from Google.

My answer was to stop trusting page load as the signal. I turned off the tracker's automatic pageview and fire it manually only after a human engagement signal, a real scroll of at least 150 pixels or an interaction. Load-and-bounce traffic never registers at all. Custom events still send normally, so nothing else in the setup changes.

This will make your numbers go down

Gating pageviews on engagement cut my reported traffic noticeably, and that is the point: the number got smaller and more true. If you do this, note the date you changed it, because your year-over-year comparison is now broken and you will confuse yourself in six months.

Rybbit exposes the same lever through its site settings, where automatic initial pageview is a toggle rather than something you have to code around. Ask the assistant to wire the manual call behind whatever engagement signal suits your site, and to skip tracking entirely for known bot user agents before the script even loads.

D. Conversions: track them on the server, not in the browser

Every conversion that matters on this site is an outbound click, and outbound clicks are exactly what an adblocker eats. So I do not track them in the browser at all.

Every CTA points at an internal redirect route on my own domain. The route looks up the destination, fires the event server-side, and redirects. An adblocker cannot intercept it, because from the browser's point of view nothing happened except a navigation to a page on the same site.

textVisitor clicks CTA
  → /go/tool-slug?pos=hero
  → server: look up destination, fire conversion event, append UTMs
  → 302 to the destination

Mirror the event to two sinks if you can. My redirect route sends each click to both Umami and PostHog, best-effort and concurrent, so one being down or misconfigured never loses the click. It also means click data sits next to pageview data in both tools and can be joined against it, rather than living in a silo.

Once events are firing, the goals themselves are a conversation rather than a dashboard exercise. Rybbit's MCP exposes create_goal, get_goals, save_funnel and analyze_funnel, so you can say what you want and let it build the configuration:

A goal and funnel setup, spoken

Create a goal for the newsletter_signup event. Then build a funnel from any blog page, to a scroll depth over 50 percent, to newsletter_signup, and tell me the drop-off at each step for the last 30 days.

That is a job that used to mean a tag manager container, a test deploy and a dashboard build. It is now one message, and the funnel comes back computed. Use analyze_funnel first to check an idea without saving it, then save_funnel only for the ones you will look at again.

E. Revenue: be honest about where attribution ends

If you sell on your own site, this part is easy. Fire a purchase event with the amount as a numeric property and you are done:

javascriptwindow.rybbit.event("purchase", {
  plan: "pro",
  price: 49.99,
});

Then aggregate it. Rybbit's MCP includes run_query, a read-only ClickHouse SQL tool against a site-scoped events table, so revenue reporting is a question rather than a report build. Ask for revenue by landing page for the last quarter and you get numbers back, not a chart you then have to read.

If you make money through affiliate links, which is my situation, I have to be straight with you about the limit here. Your attribution ends at the click. The conversion happens on the vendor's site, in the vendor's checkout, and comes back to you as a line in a network dashboard days or weeks later, often with no way to tie it to the page that sent it.

No analytics tool solves that, and any post telling you otherwise is selling something. What you can do is make the join possible: pass a consistent identifier in your outbound URL, keep your click data queryable, and reconcile against the network export on whatever cadence you can stand. Partial attribution you understand beats a revenue number you cannot explain.

The weekly review, and why I do not run it on a schedule

This is where the setup starts paying you back. Once the MCP is connected, a weekly review is a prompt rather than a process, and it can reach across every data source you have connected rather than just analytics.

The weekly prompt

Pull last week against the week before: sessions, top pages, entry pages and goal conversions. Cross-reference Search Console for impressions and position on those same pages. Flag any page where impressions rose but clicks did not, and any page where traffic dropped more than 20 percent. Then give me three things to do this week, ordered by expected impact, and say what you would need to see to know if they worked.

The last clause is the one that matters. Without it you get a summary. With it you get a hypothesis you can check next week, and the review becomes a loop instead of a report.

The sources worth wiring in beyond analytics:

  • Search Console: impressions and average position against your own traffic. Rising impressions with flat clicks is a title and description problem, and it is invisible in analytics alone.
  • Your email platform: which campaigns actually sent traffic, joined to what that traffic then did on the site.
  • Ad platforms: spend next to on-site behaviour, so you can see which campaigns buy engaged readers rather than bounces.
  • Your own database: signups, trials, whatever your real outcome is. This is the join that turns traffic analysis into business analysis.

Run it, do not schedule it

I deliberately do not run this on a cron. An unattended job that reads your analytics and writes a recommendation produces output nobody reads, on a schedule, forever. Worse, an agent with write access running unsupervised can change your goal configuration at 3am and you find out in a month when a number looks wrong.

Save it as a command you invoke instead. In Claude Code that is a slash command, a file in your project with the prompt in it, and running it takes about as long as opening a dashboard would have. You stay in the loop, the agent stays supervised, and you only generate the report on the weeks you are going to act on it.

If you want the write side automated, scope the credential so it cannot do damage. Read-only for anything that runs without you watching, and save the goals-and-funnels write scope for sessions you are present for.

What this actually replaces

Count the pieces. A tag manager container, because the events are in the code and an agent writes them. A dashboard tool, because you ask questions instead of building charts. A measurement-plan workshop, because the assistant reads the site and proposes the taxonomy. A weekly reporting slot, because the report is a prompt.

I want to be fair to the old way. That work was genuinely skilled, it took years to get good at, and clients got value from it. But most of the value was in navigating complexity that the tools imposed, not complexity that the problem required. When the tool removes it, the skill it demanded stops being worth billing for.

What is left is the part that was always the actual job: knowing which question is worth asking, and being honest about what the answer means. No MCP server does that for you.

If you have not picked a tool yet, my roundup of web analytics tools sorts 17 of them by exactly this: which ones ship a vendor-built MCP server, which rely on community projects, and which gate API access behind a plan well above their entry price. Two of the most popular privacy-first tools cannot be queried at all on their cheapest plans, which makes them a poor fit for everything described above.

And if MCP itself is new to you, start with the practical guide to using MCP with marketing tools, which covers what a server is and what changes once one is wired up.

Start with the tracking script and the MCP connection. Everything else in this post is a conversation you can have once those two things exist, and you will get further in an afternoon than a month of tag manager work ever got me.

Read next

Joonas Rotko

Joonas Rotko

Author & founder of That Marketing Buddy

I score marketing software for AI-stack fit (MCP, API, agent-readiness), backed by 10+ years in digital marketing.

WEEKLY NEWSLETTER

The marketing tools worth your money, scored for AI agent readiness

Every week I score the tools for MCP, API, and agent-readiness, then email you the ones that actually deliver, what plugs into Claude and Cursor, the pricing changes, and what to skip. No fluff, no daily spam.

Join for free. One email a week, unsubscribe anytime.