Guide

Set up Google Analytics 4

Google-side configuration · about 20 minutes
What you end up with

A measurement id firing on your site, data visible in Realtime, and a numeric property id plus read access for anything that reads the property over the API — anacraft included. Every step below happens in Google's console, not in anacraft.

Working in Claude Code? The repo ships this guide as a skill: clone it and ask to configure Google Analytics, and Claude walks the steps with you.

01Create the account and property

Everything starts at analytics.google.com under Admin, the gear at the bottom left. GA4 nests Account → Property → Data stream; knowing which level a setting lives on saves most of the hunting.

An account is an organisation-level container — one is usually enough. Under it, Admin → Property → + Create → Property:

The business details and objectives screens only shape which reports appear by default. Nothing there is load-bearing.

02Add a web data stream

Admin → Data collection and modification → Data streams → Add stream → Web. Give it the canonical origin, https:// included, and leave Enhanced measurement on — it collects page views, scrolls, outbound clicks, site search, video engagement, file downloads and form interactions without a line of code.

The stream page then shows your Measurement ID, G-XXXXXXXXXX. That is what the tag needs. It is not the property id, which is the single most common mix-up when wiring up a tool — see step 06.

03Install the tag

The cheapest correct install is the gtag.js snippet, in <head>, on every page:

<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

Two rules prevent most broken installs: paste the id in both places, and keep it to one GA4 tag per page — a second config for the same id double-counts every view. If Google Tag Manager is already on the site, configure GA4 inside GTM instead of adding this snippet, and remember to publish the container.

Single-page apps need one addition. The snippet fires a pageview on load, and client-side navigation changes the URL without a reload, so every later route goes uncounted until you send it yourself:

// on every route change
gtag('event', 'page_view', {
  page_path: location.pathname + location.search,
  page_location: location.href,
  page_title: document.title,
});

On Next.js, @next/third-parties handles both the placement and the route changes. On WordPress, Site Kit or any GA4 plugin does; editing header.php works too, but only from a child theme or the next update erases it.

If your site serves the EEA or UK, set Consent Mode v2 defaults before the tag loads and update them when the visitor chooses. A consent platform emits those calls for you; ordering is the whole game.

Or sidestep the banner entirely with gtag('config', 'G-XXXXXXXXXX', { client_storage: 'none' }). GA4 then writes no cookie, so there is no device storage to ask consent for. You lose returning-visitor and multi-day attribution — every visit counts as new — and keep page views, countries, referrers and Realtime. This site does exactly that, which is why you are reading it without dismissing a banner. Do not combine the two: an analytics_storage: 'denied' default downgrades every hit to a consent ping that GA4 reports only through modelling, so a small site sees less data, not more.

04Verify data is arriving

In this order, because each rules out a different failure.

Realtime — Reports → Realtime, with the site open in another tab. Users appear within about 30 seconds. This proves the tag fires and the id is right.
DebugView — Admin → DebugView, if Realtime stays empty. It shows events one by one, but only for a debug-enabled session: use the Google Analytics Debugger extension, or send debug_mode: true in the config call.
Standard reports — empty for 24 to 48 hours. That is normal. Never debug an install against Reports; debug against Realtime.

05Change these before you forget

Data retention. Admin → Data collection and modification → Data retention. Event data defaults to 2 months; set 14 months unless policy says otherwise. It governs explorations and funnels rather than the standard reports, and it is not retroactive — data already aged out is gone.

Internal traffic. Admin → Data streams → your stream → Configure tag settings → Define internal traffic, then Admin → Data filters. A new filter is created in Testing, where it does nothing; switch it to Active once you have confirmed it tags your own visits correctly.

Unwanted referrals. Same tag settings menu — add a payment or auth host such as Stripe or PayPal, which would otherwise break session attribution and inflate direct traffic.

Cross-domain measurement belongs there too, if one journey spans two domains. Google Signals, under Data collection, adds demographics and cross-device reporting at the cost of more thresholded rows — an opt-in decision, not a default.

06Key events, and the id that tools want

Key events are what GA4 called conversions before 2024. Mark one at Admin → Events with the Mark as key event toggle, or create it by name at Admin → Key events if it has not fired yet. For a click or form submit that enhanced measurement misses, send gtag('event', 'signup_complete') and mark it once it shows up, which can take a day.

Your property id is at Admin → Property → Property details, top right: a bare number like 397412345. Three ids get confused constantly, and only one of them belongs in an API call.

ValueLooks likeBelongs in
Property id397412345The Data and Admin APIs, and any reporting tool — craft use 397412345
Measurement idG-XXXXXXXXXXThe on-page tag, and nowhere else
Stream id1234567890Stream-level admin calls only

07Grant access

Admin → Property access management → +, or Account access management to cover every property at once. Least privilege first:

RoleCan
ViewerSee reports and explorations. The right role for a dashboard or a read-only API client.
AnalystViewer, plus create and edit shared explorations and audiences.
EditorAnalyst, plus edit property settings, streams and events.
AdministratorEditor, plus manage users. Keep this to one or two people.

The No Cost Metrics and No Revenue Metrics restrictions apply on top of the role. People are added by email and see the property on their next sign-in.

08API access, when a tool needs it

Three things have to line up, and each missing one produces its own error.

The Cloud project carries only the API enablement and its own quota — it does not own the Analytics data and need not sit in the same organisation. Running your own project is also how you stop sharing a rate limit with everyone else using the same tool.

Many tools ship their own OAuth client and need none of this. anacraft does: craft login works with no Google Cloud project at all.

09When it is not working

SymptomUsual cause
Realtime emptyTag not in the served HTML, wrong id, or an ad blocker, tracking prevention or CSP dropping the request. Check DevTools → Network for collect.
Empty only for youAn active internal-traffic filter is removing exactly the visits you are testing with.
Realtime fine, reports emptyThe 24–48 hour processing delay, or a date range that excludes today.
Every metric doubledTwo GA4 tags on the page — typically gtag.js and GTM both firing.
Direct traffic inflatedA payment or auth host counted as a referrer. Add it to unwanted referrals.
Explorations missing old dataData retention still at the 2-month default.
API 403 “has not been used”The Data or Admin API is not enabled on that Cloud project.
API 403 permission deniedThe account or service account lacks Viewer on that property.
API 404 on a G- idMeasurement id used where the numeric property id belongs.
API 429Quota. Poll less often, or move to your own Cloud project.

10Point anacraft at it

With the property collecting data and your Google account holding at least Viewer on it, three commands connect the dashboard:

# sign in — read-only, official builds carry their own OAuth client
$ craft login

# every GA4 property this account can read
$ craft props

# save one as the default, by numeric property id
$ craft use 397412345

Then craft opens the dashboard on real numbers, and craft live is the fastest confirmation that your tag is still firing. The quick start covers the rest of the commands.