Anchor Links and UTM Parameters: Technical Guide
Anchor links and UTM parameters both live in URLs. But they follow different rules, serve different purposes, and when mixed incorrectly, destroy campaign tracking.
This is the complete technical guide to using them together correctly.
🚨 Not sure what's breaking your tracking?
Run a free 60-second audit to check all 40+ ways UTM tracking can fail.
Scan Your Campaigns Free✓ No credit card ✓ See results instantly
Understanding URL Fragments (Anchor Links)
What Are URL Fragments?
The fragment identifier (everything after #) was designed for in-page navigation:
https://docs.site.com/guide#installation
↑
fragment
Purpose: Scroll the browser to the HTML element with id="installation".
Fragment Behavior (RFC 3986)
URL fragments follow specific rules defined in RFC 3986:
- Client-side only: Never sent in HTTP requests
- Browser-handled: Processed entirely by the browser
- After query string: Must come after
?parameters - Single fragment: Only one
#per URL (last one wins)
Why Fragments Aren't Sent to Servers
When you visit:
site.com/page?utm_source=email#section3
HTTP request sent to server:
GET /page?utm_source=email HTTP/1.1
Host: site.com
Fragment kept in browser:
#section3
The server never sees the fragment. This is by design—fragments are for client-side navigation only.
UTM Parameters: Server-Side Tracking
UTM parameters are query string parameters:
site.com?utm_source=facebook&utm_medium=cpc&utm_campaign=summer
↑
query string (sent to server)
Purpose: Track campaign attribution in analytics.
Requirements:
- Must start with
? - Multiple parameters joined with
& - Must be URL-encoded
- Must be sent to the server for tracking
The Conflict: When Fragments Come First
The Broken Structure
❌ site.com#pricing?utm_source=facebook&utm_campaign=sale
What happens:
- Browser parses URL
- Finds
#first - Treats everything after
#as fragment:pricing?utm_source=facebook&utm_campaign=sale - Sends to server:
site.com - Fragment (including UTMs): stays in browser
Result: Zero UTM parameters reach Google Analytics.
The Correct Structure
✅ site.com?utm_source=facebook&utm_campaign=sale#pricing
What happens:
- Browser parses URL
- Finds
?first → query string starts - Parses UTM parameters until
# - Sends to server:
site.com?utm_source=facebook&utm_campaign=sale - Fragment:
#pricingstays in browser for scroll behavior
Result: GA4 receives UTM parameters AND page scrolls to anchor.
😰 Is this your only tracking issue?
This is just 1 of 40+ ways UTM tracking breaks. Most marketing teams have 8-12 critical issues they don't know about.
• 94% of sites have UTM errors
• Average: $8,400/month in wasted ad spend
• Fix time: 15 minutes with our report
✓ Connects directly to GA4 (read-only, secure)
✓ Scans 90 days of data in 2 minutes
✓ Prioritizes issues by revenue impact
✓ Shows exact sessions affected
Complete URL Anatomy
RFC 3986 URL Structure
https://user:pass@host.com:8080/path/to/page?query=value&key=val#fragment
↑ ↑ ↑ ↑ ↑ ↑ ↑
scheme userinfo host port path query fragment
ORDER MATTERS:
1. Scheme (https://)
2. Authority (user:pass@host.com:8080)
3. Path (/path/to/page)
4. Query (?query=value&key=val) ← UTM parameters go here
5. Fragment (#fragment) ← Anchor goes here
UTM Parameter Placement
✅ CORRECT:
https://site.com/products/shoes?utm_source=facebook&utm_medium=paid#featured
↑ ↑
query starts fragment starts
❌ WRONG:
https://site.com/products/shoes#featured?utm_source=facebook&utm_medium=paid
↑
fragment starts (consumes everything after)
Real-World Implementation Examples
Example 1: Product Launch Landing Page
Goal: Drive traffic to specific section with campaign tracking.
Correct URL:
https://shop.com/new-arrivals?utm_source=instagram&utm_medium=story&utm_campaign=spring2024&utm_content=swipe-up#hero-banner
Behavior:
- User clicks in Instagram story
- Lands on /new-arrivals page
- Page scrolls to #hero-banner section
- GA4 receives full campaign attribution
Example 2: Email Newsletter
Goal: Link to blog post section with tracking.
Correct URL:
https://blog.com/ultimate-guide?utm_source=newsletter&utm_medium=email&utm_campaign=weekly-digest&utm_content=section3-link#implementation-steps
Behavior:
- User clicks in email
- Opens blog post
- Scrolls to #implementation-steps
- Email campaign tracked in GA4
Example 3: Multi-Anchor Navigation
Common mistake:
❌ site.com#header?utm_source=facebook#main-content
Why it fails:
- Only one fragment allowed per URL
- Browser uses last
#(main-content) - First
#starts fragment, consuming?utm_source=facebook
Correct approach:
✅ site.com?utm_source=facebook#main-content
Pick ONE anchor. You can't have multiple.
Technical Validation Methods
Method 1: Browser DevTools
// Open browser console on your landing page
const url = new URL(window.location.href);
console.log('Full URL:', url.href);
console.log('Query string:', url.search);
console.log('Fragment:', url.hash);
console.log('UTM Source:', url.searchParams.get('utm_source'));Expected output (correct structure):
Full URL: https://site.com/page?utm_source=facebook#pricing
Query string: ?utm_source=facebook
Fragment: #pricing
UTM Source: facebook
Output when broken:
Full URL: https://site.com/page#pricing?utm_source=facebook
Query string: (empty string)
Fragment: #pricing?utm_source=facebook
UTM Source: null
Method 2: Server-Side Validation
# Python example - check what server receives
from flask import Flask, request
app = Flask(__name__)
@app.route('/landing')
def landing():
utm_source = request.args.get('utm_source')
print(f"Query string received: {request.query_string}")
print(f"UTM Source: `{"{"}{"{"}utm_source{"}"}{"}"}}`")
# If utm_source is None, fragment came before query
if utm_source is None:
print("ERROR: No UTM parameters received. Check URL structure.")Method 3: Google Analytics Debugger
// Add to your site for debugging
window.addEventListener('load', () => {
const params = new URLSearchParams(window.location.search);
const hasUTMs = params.has('utm_source') || params.has('utm_medium');
if (!hasUTMs && window.location.hash.includes('utm_')) {
console.error('UTMs detected in fragment! They will not track.');
console.error('Fragment:', window.location.hash);
}
});Edge Cases and Gotchas
Edge Case 1: JavaScript-Generated Links
// ❌ WRONG - builds fragment first
const link = `${"{"}{"{"}baseUrl{"}"}{"}"}}#${"{"}{"{"}anchor{"}"}{"}"}}?${"{"}{"{"}utmParams{"}"}{"}"}}`;
// ✅ CORRECT
const link = `${"{"}{"{"}baseUrl{"}"}{"}"}}?${"{"}{"{"}utmParams{"}"}{"}"}}#${"{"}{"{"}anchor{"}"}{"}"}}`;Edge Case 2: URL Builder Concatenation
// ❌ WRONG - appends UTMs to existing fragment
function addUTMs(url, utms) {
return url + '?' + utms; // Dangerous if url has fragment
}
// ✅ CORRECT - checks for existing fragment
function addUTMs(url, utms) {
const [base, fragment] = url.split('#');
const separator = base.includes('?') ? '&' : '?';
return `${"{"}{"{"}base{"}"}{"}"}}${"{"}{"{"}separator{"}"}{"}"}}${"{"}{"{"}utms{"}"}{"}"}}${fragment ? '#' + fragment : ''}`;
}Edge Case 3: Third-Party URL Shorteners
Some URL shorteners preserve fragments, others don't:
bit.ly/abc123 → expands to → site.com?utm_source=x#pricing ✅
rebrandly.com/xyz → expands to → site.com#pricing?utm_source=x ❌
Solution: Always test shortened URLs before using in campaigns.
Best Practices Checklist
For Developers
✅ Use URL constructor APIs (not string concatenation) ✅ Validate URL structure in tests ✅ Add lint rules for URL building ✅ Document URL structure in style guide ✅ Use TypeScript for URL parameter types
For Marketers
✅ Always put ? before # in campaign URLs
✅ Test every campaign URL in GA4 Real-Time
✅ Use URL validation tools before launch
✅ Document URL structure in campaign templates
✅ Train new team members on correct structure
✅ Fixed this issue? Great! Now check the other 39...
You just fixed one tracking issue. But are your Google Ads doubling sessions? Is Facebook attribution broken? Are internal links overwriting campaigns?
• Connects to GA4 (read-only, OAuth secured)
• Scans 90 days of traffic in 2 minutes
• Prioritizes by revenue impact
• Free forever for monthly audits
Join 2,847 marketers fixing their tracking daily
FAQ
Can I use multiple anchors in one URL?
No. Browsers only support one fragment per URL. If you have multiple # symbols, only the last one is used.
What happens to the fragment in server logs?
Fragments never appear in server logs because they're not sent in HTTP requests.
Do URL shorteners break fragments?
Most preserve fragments correctly. But some redirect services may strip or reorder them. Always test before using in production.
Can I track fragment interactions in GA4?
Yes, using custom JavaScript events. Fragments aren't automatically tracked, but you can send events when users click anchor links.
Does this affect SEO?
No. Google crawls query strings but ignores fragments (except in AJAX crawling, which is deprecated). Your UTM parameters don't affect SEO.
How do I validate URLs programmatically?
Use the URL API in JavaScript, or URL parsing libraries in other languages. Check that url.search contains UTMs and url.hash contains the anchor.
Conclusion
Anchor links and UTM parameters work together perfectly when structured correctly.
The rule:
domain.com/path?query_parameters#fragment
ALWAYS: ? before #
Implementation:
- Use URL APIs, not string concatenation
- Validate structure before launch
- Test in GA4 Real-Time reports
- Document in your style guide
Get this right once, and both your marketing tracking and user experience work flawlessly.
Technical Reference: Fragment Before Query Validation Rule