There is a common moment for WordPress site owners in the AI era. You read that ChatGPT and Claude are crawling the web at enormous scale, you open Google Analytics to see how much of that is hitting your site, and you find... nothing. No GPTBot in your traffic sources. No ClaudeBot in your user agents. Not a trace.
The tempting conclusion is that AI bots aren't visiting you. The correct conclusion is that you're looking in a place where they cannot exist. This isn't a flaw in GA4. It is a direct consequence of where GA4 measures - and no configuration, filter, or plugin will change it.
How JavaScript analytics actually counts a visit
GA4, Plausible, Fathom, Matomo's default tag, and every other JavaScript-based tracker work the same way. Your page includes a small script. When a browser loads the page, it executes that script, which collects context - screen size, referrer, user agent - and fires a request to the analytics vendor's collection endpoint. That collection hit is the pageview. No script execution, no pageview, no visitor.
This design has real advantages: it filters out most junk traffic by default, it can measure client-side things like scroll depth, and it works even when your HTML is served from a cache the analytics vendor never touches. For measuring humans, it's a reasonable trade.
Didn't analytics already solve bots?
Long-time GA users will remember the "exclude known bots and spiders" checkbox, and GA4 applies that filtering automatically using industry bot lists. It is easy to conclude the bot problem is handled. But look at what that filter actually does: it subtracts declared bots from the traffic the JavaScript tag already measured. It is a cleanup pass on the human-shaped data, built for an era when the worry was spam crawlers inflating your pageviews.
A filter can only remove what was counted. It cannot add back the crawlers that never executed the tag in the first place - which is essentially all of them. So GA4's bot handling and server-side bot visibility are not two versions of the same feature. One hides bots from your human reports; the other is the only way to see them at all. You want both, and they do not compete.
Crawlers fetch HTML. They do not run your scripts.
A crawler's job is to retrieve documents, not to render experiences. GPTBot, ClaudeBot, PerplexityBot, CCBot, Googlebot's text crawler - they issue an HTTP GET, take the HTML your server returns, and move on. Executing JavaScript is expensive at crawl scale (Google renders pages in a separate, delayed pipeline for exactly this reason), and for an AI training crawler there is simply no need: the text is already in the HTML.
So the analytics tag ships to the crawler along with the rest of your page, and it is never executed. From GA4's point of view, the visit never happened. Multiply that by every AI crawler, every search engine, every scraper, every uptime monitor, and you get the structural reality: JavaScript analytics measures the subset of your traffic that behaves like a person. Everything else is invisible - not undercounted, invisible.
What the server sees instead
Every request that reaches your server - human or machine - arrives as an HTTP request with headers, including a user-agent string. The major AI operators identify themselves honestly in that string, and publish documentation saying so:
Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot Mozilla/5.0 (compatible; ClaudeBot/1.0; +claudebot@anthropic.com) Mozilla/5.0 (compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)
Measured at the server, these are not exotic edge cases. They are plain rows in the request stream, as countable as any human pageview. That is the entire premise of server-side analytics: measure where all traffic must pass, not where only humans perform. Viz does this inside WordPress itself, classifying each request as it arrives, so the AI share of your traffic becomes an ordinary chart instead of a mystery.
Four places to stand: browser, edge, origin, application
"Measure at the server" hides a real choice, because a modern WordPress request can be answered from several places, and each one sees a different slice of reality:
- The browser sees only script-executing visitors - the GA4 view. Rich behavioral detail, zero machine visibility.
- The CDN edge sees every request, including ones served from edge cache that never touch your server. But it lives outside your stack, its analytics are usually aggregate and plan-gated, and it knows nothing about your site's structure.
- Origin access logs see whatever passes the cache, in raw text form - complete but unclassified, often rotated away in days, and frequently unreadable on managed hosting.
- The application - WordPress itself - sees every request that reaches PHP, with full context: which post, which feed, which API route. This is where Viz measures, which is why its data can say "GPTBot read these 40 articles" rather than "something fetched these 40 URLs."
The honest caveat: a full-page cache hit answered at the edge is invisible to any WordPress plugin, because WordPress never runs for it. In practice crawlers sweep long-tail pages where cache hit-rates are low, so their activity shows up clearly at the application layer - but if you want a number for cached hits too, edge logs are the complement, not the substitute. No single vantage point is complete; the application layer is the one that combines machine visibility with meaning.
Server-side tagging does not fix this either
A tempting objection: GA4 offers server-side tagging, so surely that makes it server-side analytics? No - it relocates the collection endpoint, not the measurement point. With a server-side GTM container, the browser still has to execute the tag and send the event; the container just proxies it onward from infrastructure you control. A crawler that never runs JavaScript never generates the event, and no amount of server-side plumbing conjures it back. The measurement point - the place where a visit must prove it exists - is still the browser.
The honest limits of user-agent identification
Server-side measurement is not magic, and anyone selling it as all-seeing is overselling. Two caveats matter:
- User agents can be forged. The string is self-reported. A scraper can claim to be Chrome, or even claim to be Googlebot. Well-behaved operators publish IP ranges or support reverse-DNS verification so their identity can be confirmed; bad actors do neither. A classification system tells you the truth about honest traffic and flags the dishonest remainder as what it is: unverified.
- Some automation runs real browsers. Headless Chrome executes JavaScript and can appear in GA4 looking human-ish. This traffic cuts the other way: it pollutes your human numbers rather than hiding from them.
The practical takeaway is not that identification is hopeless - the overwhelming majority of AI crawl volume comes from self-identifying bots run by large companies with reputations to protect. It is that a good analytics tool should be explicit about certainty, which is why an honest system keeps an "unknown" bucket instead of forcing every request into a confident label.
Why your "direct" traffic is polluted
There is a second-order effect worth understanding. When automation does execute JavaScript, or when a human arrives from an AI assistant that strips the referrer, GA4 files the visit under "direct" - the bucket that really means "no idea." Direct traffic has always been a junk drawer, but the AI era is stuffing it fuller: assistant-referred humans without referrers, headless browsers, and app webviews all land there. So JavaScript analytics doesn't just miss the machines; it quietly degrades the signal quality of what it does count.
We have been here before, in reverse
There is a historical irony worth savoring. In the log-analyzer era - AWStats, Webalizer, early 2000s - everyone measured at the server, and the standing complaint was the opposite one: the numbers were inflated by bots, and separating machine hits from human visits was the hard part. JavaScript tagging won precisely because it solved that problem structurally: only browsers run scripts, so only humans get counted. The industry traded machine visibility away on purpose, and it was a good trade at the time, because machine traffic was noise nobody valued.
What changed is not the measurement - it is the meaning of the machines. Bot traffic stopped being noise the day models started training on your archive and answer engines started deciding whether to cite you. The pendulum now has to swing back toward the server, but with a requirement the AWStats era never met: classification. Raw server counting failed once because it could not tell humans from machines. The useful synthesis is measuring at the server with the machine traffic sorted into named, meaningful categories - which is the entire design brief behind Viz.
The bill you are already paying
Even if you decide the strategic questions can wait, there is a bluntly operational reason to see this traffic: you are paying to serve it. Every crawl consumes origin compute, database queries, and bandwidth - resources your hosting plan meters or your server budget absorbs. A crawler sweeping ten thousand archive pages is a real load event, and on shared or metered WordPress hosting it can be the difference between plan tiers. Site owners routinely discover that a meaningful fraction of their "performance problem" is actually an uninvited crawl schedule. You do not need a licensing strategy to care about that; you just need the bill. Seeing which machines cost you what, per bot and per day, turns a vague hosting anxiety into a fixable line item.
Three numbers, all true
Put it together and you can predict a confusing but common experience: on the same day, your CDN dashboard reports 40,000 requests, a server-side tool reports 12,000, and GA4 reports 3,000 sessions. None of them is wrong. The CDN counted every asset request including cached ones, the application counted dynamic pageview-shaped requests from humans and machines, and GA4 counted script-executing humans grouped into sessions. The lesson is not "analytics is unreliable." It is that a traffic number is meaningless without knowing where it was measured - and that the gap between those numbers is itself the most interesting data you have, because the gap is mostly machines.
What to do with this
If you only take one thing from this article: your analytics stack answers the question "what are humans doing on my site?" and was never designed to answer "what is reading my site?" Those are different questions, and the second one now matters - for server costs, for content licensing posture, and for understanding how AI engines learn about your brand. A practical starting sequence:
- Get any server-level view of your traffic - even a week of data will recalibrate your assumptions about how much of your audience is machines.
- Identify your top three crawlers by volume and read up on what each one feeds (our bot directory covers the major ones, one page each).
- Stop treating "direct" as a channel in reports. Treat it as a residual, and expect it to keep growing as assistant-referred visits arrive without referrers.
- Only then decide posture - block, allow, meter - per bot, from evidence. Policy before measurement is guesswork with confidence.
Start there, and the invisible half of your traffic turns out to have been perfectly visible all along - just not from where you were standing.