Add Google Analytics 4 without another plugin
Drop your GA4 tag into wp_head in under a minute, scoped to just the pages you care about.
The usual way to add Google Analytics to WordPress is to install a plugin that does one thing: print
a script tag in the <head>. That plugin then ships an admin menu, a settings page, a dashboard
widget, an upsell notice, and its own update cycle — all to output eight lines of JavaScript.
Here is the whole job as a snippet.
The snippet
New Snippet → Content (PHP + HTML), run location Site Wide Header. Paste your tag, with your own measurement ID:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>
Name it something you will recognise in a list — Google Analytics 4 — and publish. That is the
feature complete. Site Wide Header maps to wp_head, so the tag lands inside <head> on the
front end, exactly where Google asks for it.
Content snippets start in HTML mode, so there is no <?php tag to open and nothing to escape. Write
the markup as Google gives it to you.
Don’t track yourself
Every analytics plugin has a “don’t track logged-in users” checkbox. Here it is a condition rather than a setting.
Expand Advanced Conditional Logic, switch it on, and add one condition:
- User / Logged-in — equal — False
Now the tag prints for visitors and not for you, your editors, or the client refreshing their own homepage. If you would rather only exclude administrators, use User Role — not includes in — Administrator instead; a logged-out visitor holds no roles at all, so they still match.
Because these are conditions rather than PHP, the snippet code stays exactly the eight lines Google published. Nothing to re-read in six months.
Scoping it further
The same panel covers the rest of the usual requests:
Staging should not report to production. Add Page / URL — does not includes —
staging. and the tag stops firing on the staging hostname, with no environment check in the code.
Only track the shop. Page / Post Type — includes in — Product. Remember this only matches singular requests, so add a second group for the archive if you need it.
A campaign pixel with an expiry date. Date / Date Range — within — your campaign window. The tag switches itself off when the campaign ends, which is the one part of this job everybody forgets.
Adding the GTM noscript half
If you are using Google Tag Manager rather than gtag, the container needs a second block immediately
after the opening <body> tag. That is a separate snippet with run location Site Wide Body
Open:
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
Two snippets, both visible in one list, both switched off with one click each.
Site Wide Body Open relies on your theme calling wp_body_open(), which has been standard since WordPress 5.2. A very old theme may not, in which case the noscript block has nowhere to go.
Why this is worth doing
The snippet version has no settings page, no database rows, and no update to install next month. It is eight lines of vendor-supplied JavaScript sitting in a file you can read, in a list next to every other thing you have added to the site.
And when you want it gone, you delete one row. There is no leftover options table, no orphaned
scheduled event, and no “deactivate and delete” dance. The tag was never anywhere but wp_head.
Same pattern works for Meta Pixel, Plausible, Fathom, Clarity, Hotjar and every other analytics snippet vendors hand you — they are all a script tag and an ID.