When ChatGPT, Perplexity, Gemini or Claude cite your site in an answer, they often link to the exact passage they quoted using a text fragment - a URL ending in something like #:~:text=Sourdough%20Cheese%20Starter%20Kit. Click the citation and the user lands on your page with that text highlighted.
These visits are one of the strongest signals available right now for which of your content is showing up inside generative answers. But almost no GA4 setup catches them. Here's why, and how to fix it.
Why standard analytics doesn't see text fragments
Two things trip up most setups:
- Servers never see fragments. Anything after
#in a URL is client-side only. Web server logs, CDN logs and any server-side analytics will not contain them. - Even client-side, Chrome strips fragments from
document.location.href- deliberate browser behaviour to avoid leaking fragment directives via JavaScript. Because GA4'spage_locationis built fromdocument.location.href, the fragment is gone before GA4 ever sees it.
The result: even if a quarter of your traffic came from LLM citations using text fragments, GA4 would show nothing distinguishable about it.
The fix is a small Google Tag Manager change that pulls the original URL - fragment intact - from the browser's Performance API, and sends it to GA4 as a custom event.
The setup (around 15 minutes in GTM)
You'll need:
- A Google Tag Manager container live on your site
- The GA4 Measurement ID for the destination property (
G-XXXXXXXX) - Edit access to both
1. Create a Custom JavaScript variable for the URL with fragment
GTM → Variables → User-Defined Variables → New → Custom JavaScript. Name it Page URL with Fragment Directive. Paste:
function() {
try {
var nav = performance.getEntriesByType('navigation')[0];
return (nav && nav.name) || document.location.href;
} catch (e) {
return document.location.href;
}
}
performance.getEntriesByType('navigation')[0].name returns the URL the navigation was initiated to, with the text fragment preserved.
2. Create a second variable to extract the readable text
Same place - New → Custom JavaScript. Name it Text Fragment Value:
function() {
try {
var url = (performance.getEntriesByType('navigation')[0] || {}).name || document.location.href;
var idx = url.indexOf(':~:text=');
if (idx === -1) return undefined;
var raw = url.slice(idx + 8);
var parts = raw.split('&text=').map(function(p) {
try { return decodeURIComponent(p); } catch (e) { return p; }
});
return parts.join(' | ').slice(0, 100);
} catch (e) {
return undefined;
}
}
URL-decoding gives you Sourdough Cheese Starter Kit rather than Sourdough%20Cheese%20Starter%20Kit. Multiple fragments on one URL (rare but legal) get joined with | . The 100-character cap matches GA4's event-parameter limit.
3. Create a trigger for text-fragment landings
Triggers → New → Page View → "Some Page Views". Condition:
Page URL with Fragment Directive contains #:~:text=
4. Create the GA4 event tag
Tags → New → Google Analytics: GA4 Event.
- Measurement ID: your
G-XXXXXXXX - Event Name:
text_fragment_click(name it for the meaning -llm_text_landingworks just as well) - Event Parameters:
page_path→{{Page Path}}text_fragment→{{Text Fragment Value}}
Fire on the trigger from step 3.
If your container doesn't already have a Google Tag (Configuration tag) for that Measurement ID - i.e. nothing else on the page is loading GA4 - add one alongside, firing on Initialization - All Pages. Without it, the event has no GA4 destination to attach to and silently drops.
5. Submit and publish
GTM → Submit → name the version → Publish.
6. Register the parameter as a custom dimension in GA4
The step most setups forget. Until registered, the value is sent and stored on each event but isn't selectable in standard reports.
GA4 → Admin → Custom definitions → Create custom dimension:
- Dimension name: Text Fragment
- Scope: Event
- Event parameter:
text_fragment
Verifying it works
Open https://yoursite.com/#:~:text=any-test-string in a normal browser. In GA4 → Reports → Realtime, find the "Event count by Event name" card and click your text_fragment_click row. Page through the parameters and click text_fragment - the highlighted strings appear with a count.
For deeper per-event inspection, Admin → DebugView shows the full parameter payload of every event from any session with ?gtm_debug=1 (or the GA Debugger Chrome extension active).
Standard reports (Engagement → Events, Explore) populate the new dimension within 24-48 hours.
Viewing the data in GA4
Once events start flowing, there are four places to look - each with a different latency and depth.
Realtime (instant, last 30 minutes)
GA4 → Reports → Realtime. In the "Event count by Event name" card, click the text_fragment_click row. The card flips to show event parameters; page through to text_fragment and click it. The actual highlighted strings appear with a count beside each.
This is your fastest sanity check after publishing the GTM container - you should see test hits within seconds of loading a fragment URL.
DebugView (live per-event detail)
GA4 → Admin → DebugView. Shows every individual event from sessions with debug mode active (the GTM preview window does this automatically; on a normal browser, append ?gtm_debug=1 to the URL or install the GA Debugger Chrome extension). Each event shows the full parameter payload - including the decoded text_fragment value - so you can spot-check the variable is resolving correctly before relying on it in reports.
Standard reports (24-48 hour lag)
Once GA4 has finished processing, the registered Text Fragment custom dimension becomes selectable in standard reports. GA4 → Reports → Engagement → Events → click the text_fragment_click row to open its detail view. Add Text Fragment as a secondary dimension to break event counts down by the actual quoted text.
Explore (sortable table over time)
For a fully customisable view: GA4 → Explore → Free-form. Drag Text Fragment into Rows, Event count into Values, and add a filter where Event name equals text_fragment_click. You now have a leaderboard of which exact passages on your site are being most cited by LLMs over whatever date range you set. Add Page path and Session source as additional rows to see which pages and which AI tools the citations come from.
What this gives you
- Which pages on your site are being cited by LLMs
- The specific passages being quoted
- LLM-driven traffic volume over time
- Cross-referenced with
page_referrer: which AI tools are sending the most fragment-cited traffic
What it doesn't give you
- No retrospective data. Fragments aren't in historical server logs and aren't in existing GA4 hits. The clock starts the day you publish.
- 100-character cap on the fragment value - long quotes are truncated.
- PII risk is theoretical but worth knowing about: whatever is in the fragment lands in GA4. Inspect the first batch of values before relying on them in widely-shared reports.
- Bot traffic can also follow fragment URLs - filter or segment if you need a clean signal.
The full setup takes under fifteen minutes once you've got the right access, and it's the cleanest first signal we've seen for measuring whether the LLM ecosystem is actually driving traffic to your site - not just citing you.