Why UTM Shows '%20' Literal Instead of Space: Double-Encoding Explained
Fix campaign names showing literal %20, %2520, and %26 characters in Google Analytics. Understand why double-encoding creates these artifacts and how to prevent them.
Why UTM Shows '%20' Literal Instead of Space: Double-Encoding Explained
"My Google Analytics showed campaign names like 'summer%20sale' and 'email%26newsletter.' I thought it was a display bug. Nope. We'd been double-encoding URLs for 4 months. 34,000 sessions attributed to gibberish campaign names."
The Problem
What you see in Google Analytics:
Campaign Name Sessions
summer%20sale 2,456
black%20friday%202024 1,923
email%26newsletter 1,234
save%2550%25 892
What you expected:
Campaign Name Sessions
summer-sale 6,505 (all combined)
Why it's happening: Double-encoding
Table of contents
- The Problem
- The Technical Explanation
- Single Encoding (Correct)
- Double Encoding (Problem)
- Common Literal Artifacts
- Literal %20 (Space)
- Literal %26 (Ampersand)
- Literal %2520 (Double-Encoded Space)
- Literal %3D (Equals)
- Quick Diagnosis Guide
- If You See This → It Means That
- Detection Pattern
- How It Happens: Real Workflows
- Workflow 1: Spreadsheet → Email Platform
- Workflow 2: URL Builder → URL Shortener
- Workflow 3: API Integration
- How to Fix Literal Encoding
- Immediate Fix: Decode the Values
- Permanent Fix: Stop Double-Encoding
- Prevention Checklist
- Testing for Literal Encoding
- Pre-Launch Test
- Post-Launch Audit
- Real-World Fix Example
- FAQ
- Q: Why does my campaign show %20 instead of a space?
- Q: Can Google Analytics automatically fix this?
- Q: I see %2520 - is that worse than %20?
- Q: Will this fix itself over time?
- Q: Can I just search-and-replace %20 with spaces in Analytics?
- Q: What if I see %252520?
- Q: How do I prevent this?
- Q: Should I URL-decode my campaign names before using them?
🚨 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
The Technical Explanation
Single Encoding (Correct)
Original value: summer sale
Browser encodes: summer%20sale (space becomes %20)
Server receives: summer%20sale
Server decodes: summer sale
Analytics shows: summer sale ✅
Double Encoding (Problem)
Original value: summer sale
First system encodes: summer%20sale (space becomes %20)
Second system encodes: summer%2520sale (% becomes %25, so %20 becomes %2520)
Server receives: summer%2520sale
Server decodes ONCE: summer%20sale (still has %20!)
Analytics shows: summer%20sale ❌ (literal characters!)
Key insight: The server only decodes once. If you encode twice, one layer of encoding remains visible as literal characters.
Common Literal Artifacts
Literal %20 (Space)
Shows as: campaign%20name
Means:
- Original: "campaign name" (with space)
- Encoded once:
campaign%20name - Encoded twice:
campaign%2520name - Decoded once:
campaign%20name← What you see
Fix: Remove double-encoding
Literal %26 (Ampersand)
Shows as: sales%26marketing
Means:
- Original: "sales & marketing"
- Encoded once:
sales%26marketing - Encoded twice:
sales%2526marketing - Decoded once:
sales%26marketing← What you see
Should be: sales-and-marketing (avoid encoding entirely)
Literal %2520 (Double-Encoded Space)
Shows as: campaign%2520name
Means:
- Original: "campaign name"
- Encoded once:
campaign%20name - Encoded twice:
campaign%2520name - Encoded THREE times:
campaign%252520name - Decoded once:
campaign%2520name← What you see
This is triple-encoded!
Literal %3D (Equals)
Shows as: test%3Dvalue
Means:
- Original: "test=value"
- Encoded once:
test%3Dvalue - Encoded twice:
test%253Dvalue - Decoded once:
test%3Dvalue← What you see
😰 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
Quick Diagnosis Guide
If You See This → It Means That
| In Analytics | Original Was | Encoding Level | Fix |
|---|---|---|---|
summer%20sale | "summer sale" | Double-encoded | Decode twice, clean |
email%26news | "email & news" | Double-encoded | Decode twice, clean |
save%2550 | "save 50%" | Triple-encoded! | Decode three times |
%252520 | Space | Quadruple-encoded! | System severely broken |
summer sale | "summer sale" | Not encoded (good!) | Just use hyphens instead |
Detection Pattern
If you see % followed by two digits/letters, you have encoded characters showing literally.
function detectLiteralEncoding(campaignName) {
const encoded = campaignName.match(/%[0-9A-Fa-f]{2}/g);
if (encoded) {
return {
hasLiteralEncoding: true,
sequences: encoded,
message: 'Campaign name shows literal encoded characters',
recommendation: 'Double-encoding likely - check URL generation process'
};
}
return {
hasLiteralEncoding: false,
message: 'No literal encoding detected'
};
}
// Usage
console.log(detectLiteralEncoding('summer%20sale'));
// {
// hasLiteralEncoding: true,
// sequences: ['%20'],
// message: 'Campaign name shows literal encoded characters',
// recommendation: 'Double-encoding likely - check URL generation process'
// }How It Happens: Real Workflows
Workflow 1: Spreadsheet → Email Platform
Step 1: Google Sheets formula
=A1&"?utm_campaign="&SUBSTITUTE(B2," ","%20")
Result: https://example.com?utm_campaign=summer%20sale
Step 2: Copy from spreadsheet
Paste into Mailchimp URL field
Mailchimp sees it as text, not encoded URL
Step 3: Mailchimp "helps" by encoding
Encodes the % as %25
Result: https://example.com?utm_campaign=summer%2520sale
Step 4: User clicks
Browser decodes once: utm_campaign=summer%20sale
Analytics stores: "summer%20sale" with literal %20
Workflow 2: URL Builder → URL Shortener
Step 1: URL builder encodes
Input: "Black Friday"
Output: https://example.com?utm_campaign=Black%20Friday
Step 2: Pass to Bitly
Bitly sees: ...utm_campaign=Black%20Friday
Bitly treats as plain text, encodes it
Result: ...utm_campaign=Black%2520Friday
Step 3: Analytics
Receives: "Black%20Friday" (literal)
Shows: "Black%20Friday" instead of "Black Friday"
Workflow 3: API Integration
# System A creates URL
url = "https://example.com"
params = {"utm_campaign": "Summer Sale"} # Plain text
# Encodes properly
encoded_url = url + "?" + urllib.parse.urlencode(params)
# Result: https://example.com?utm_campaign=Summer+Sale
# System B receives this URL and "normalizes"
import requests
response = requests.get(encoded_url) # requests encodes it AGAIN
# Final URL: ?utm_campaign=Summer%2B%2BSale (double-encoded +)
# Analytics shows: "Summer%2BSale" (literal +)How to Fix Literal Encoding
Immediate Fix: Decode the Values
In Google Analytics 4:
You can't change historical data, but you can create custom dimensions:
- Admin → Data Display → Custom Definitions
- Create custom dimension: "Campaign (Decoded)"
- Use transformation:
IF campaign_name CONTAINS "%"
THEN DECODE_URI_COMPONENT(campaign_name)
ELSE campaign_name
Note: GA4 doesn't have DECODE_URI_COMPONENT natively, so this requires BigQuery
In BigQuery:
CREATE VIEW analytics.cleaned_campaigns AS
SELECT
event_date,
user_pseudo_id,
-- Decode campaign names with literal encoding
CASE
WHEN campaign LIKE '%\%%' THEN
-- Decode once
SAFE.NET.URL_DECODE(
SAFE.NET.URL_DECODE(campaign) -- Decode twice for double-encoding
)
ELSE campaign
END AS cleaned_campaign,
event_count
FROM `project.dataset.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131';Permanent Fix: Stop Double-Encoding
Rule: Never encode values before passing to systems that encode
// ❌ WRONG
function badWorkflow() {
// Encode here
const campaign = encodeURIComponent('Summer Sale'); // "Summer%20Sale"
// Pass to email platform
sendEmail({
url: `https://example.com?utm_campaign=${"{"}{"{"}campaign{"}"}{"}"}}`
});
// Email platform encodes again → double-encoded!
}
// ✅ CORRECT
function goodWorkflow() {
// Don't encode - use clean value
const campaign = 'summer-sale'; // Clean, no encoding needed
// Pass to email platform
sendEmail({
url: `https://example.com?utm_campaign=${"{"}{"{"}campaign{"}"}{"}"}}`
});
// Email platform doesn't need to encode → perfect!
}Prevention Checklist
To prevent literal encoding artifacts:
- Use clean values (a-z, 0-9, hyphens only)
- Never manually add %20 or other encoded characters
- Don't encode before passing to platforms
- Test end-to-end workflow
- Check analytics after first sends
- Validate URLs don't contain %
- Document: "No encoding needed"
Testing for Literal Encoding
Pre-Launch Test
function testForLiteralEncoding(url) {
try {
const urlObj = new URL(url);
const issues = [];
urlObj.searchParams.forEach((value, key) => {
if (!key.startsWith('utm_')) return;
// Check if value contains literal % sequences
if (/%[0-9A-Fa-f]{2}/.test(value)) {
issues.push({
param: key,
value,
issue: 'Contains literal encoded sequences',
examples: value.match(/%[0-9A-Fa-f]{2}/g)
});
}
});
if (issues.length > 0) {
console.error('❌ LITERAL ENCODING DETECTED:');
issues.forEach(issue => {
console.error(` ${issue.param}: ${issue.value}`);
console.error(` Found: ${issue.examples.join(', ')}`);
});
return false;
}
console.log('✅ No literal encoding detected');
return true;
} catch (e) {
console.error('Invalid URL:', e.message);
return false;
}
}
// Usage
const testURL = 'https://example.com?utm_campaign=summer%20sale';
testForLiteralEncoding(testURL);
// ❌ LITERAL ENCODING DETECTED:
// utm_campaign: summer%20sale
// Found: %20Post-Launch Audit
-- BigQuery: Find campaigns with literal encoding
SELECT
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'campaign') as campaign,
COUNT(*) as sessions
FROM `project.dataset.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY))
AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
AND (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'campaign') LIKE '%\%%'
GROUP BY campaign
ORDER BY sessions DESC;Real-World Fix Example
Before (literal encoding):
Analytics showed:
- "summer%20sale%202024" (2,456 sessions)
- "summer sale 2024" (124 sessions from organic)
Problem: Email campaign showing with %20 literal
Investigation:
// Check the email template URL
const templateURL = getEmailTemplateURL();
console.log(templateURL);
// https://example.com?utm_campaign=summer%20sale%202024
// Decode to see original intent
console.log(decodeURIComponent('summer%20sale%202024'));
// "summer sale 2024"
// Decode again (for double-encoding)
console.log(decodeURIComponent('summer sale 2024'));
// "summer sale 2024" (same - so it was double-encoded once)Fix applied:
// Changed email template to:
const cleanCampaign = 'summer-sale-2024';
const url = `https://example.com?utm_campaign=${"{"}{"{"}cleanCampaign{"}"}{"}"}}`;
// Result: No encoding needed anywhere in the workflow
// Analytics now shows: "summer-sale-2024" perfectlyAfter fix:
Analytics showed:
- "summer-sale-2024" (2,580 sessions) ✅ Clean!
- Old "summer%20sale%202024" (2,456 sessions) - historical only
Note: Created filter to exclude old encoded version from reports
✅ 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
Q: Why does my campaign show %20 instead of a space?
A: The URL was encoded twice somewhere in your workflow. The browser decoded it once, leaving literal %20 characters that get stored in analytics.
Q: Can Google Analytics automatically fix this?
A: No, GA4 stores exactly what it receives. You must fix the source URLs.
Q: I see %2520 - is that worse than %20?
A: Yes, %2520 means triple-encoding (encoded three times). You have more encoding steps in your workflow.
Q: Will this fix itself over time?
A: No, you must find where double-encoding is happening and eliminate that step.
Q: Can I just search-and-replace %20 with spaces in Analytics?
A: No, you can't edit historical data in GA4. Create custom reports that apply transformations, or fix URLs and move forward with clean data.
Q: What if I see %252520?
A: That's quadruple-encoding! Your workflow has serious issues. Audit every step of URL generation and passing.
Q: How do I prevent this?
A: Use values that never need encoding: lowercase letters, numbers, and hyphens only. No spaces, no special characters.
Q: Should I URL-decode my campaign names before using them?
A: If they're already double-encoded, yes - decode twice, then clean to safe characters (a-z, 0-9, hyphens). Then rebuild URLs with clean values.
Stop seeing %20 and other encoded artifacts in your analytics. UTMGuard detects double-encoding instantly and ensures your campaign names always appear clean. Start your free audit today.