URL SyntaxUpdated 2025

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.

7 min readURL Syntax

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:

Code
Campaign Name              Sessions
summer%20sale             2,456
black%20friday%202024     1,923
email%26newsletter        1,234
save%2550%25              892

What you expected:

Code
Campaign Name              Sessions
summer-sale               6,505  (all combined)

Why it's happening: Double-encoding

🚨 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)

Code
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)

Code
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

Get Your Free Audit Report

Quick Diagnosis Guide

If You See This → It Means That

In AnalyticsOriginal WasEncoding LevelFix
summer%20sale"summer sale"Double-encodedDecode twice, clean
email%26news"email & news"Double-encodedDecode twice, clean
save%2550"save 50%"Triple-encoded!Decode three times
%252520SpaceQuadruple-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.

Javascript
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

Code
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

Code
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

Python
# 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:

  1. Admin → Data Display → Custom Definitions
  2. Create custom dimension: "Campaign (Decoded)"
  3. Use transformation:
Code
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:

Sql
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

Javascript
// ❌ 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

Javascript
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: %20

Post-Launch Audit

Sql
-- 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):

Code
Analytics showed:
- "summer%20sale%202024" (2,456 sessions)
- "summer sale 2024" (124 sessions from organic)

Problem: Email campaign showing with %20 literal

Investigation:

Javascript
// 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:

Javascript
// 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" perfectly

After fix:

Code
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

Run Complete UTM Audit (Free Forever)

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.

UTM

Get Your Free Audit in 60 Seconds

Connect GA4, run the scan, and see exactly where tracking is leaking budget. No credit card required.

Trusted by growth teams and agencies to keep attribution clean.