URL Special Characters Guide: Complete Encoding Reference for UTM

UTMGuard Team
10 min readbest-practices

Special characters in URLs break tracking if not encoded correctly.

This is your complete reference for encoding special characters in UTM parameters.

🚨 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

Reserved Characters (Must Encode in Values)

These characters have special meaning in URLs. When used in UTM parameter values, they MUST be encoded:

CharacterPurposeEncodedExample Value → Encoded
SpaceWhitespace%20"spring sale"spring%20sale
&Parameter separator%26"save&win"save%26win
=Key-value separator%3D"2+2=4"2%2B2%3D4
?Query start%3F"what?"what%3F
#Fragment identifier%23"#1 brand"%231%20brand
%Encoding prefix%25"20% off"20%25%20off
+Space alternative%2B"google+ads"google%2Bads
/Path separator%2F"web/mobile"web%2Fmobile

Why These Characters Are Reserved

& (Ampersand)

✅ CORRECT USAGE (separator):
?utm_source=facebook&utm_medium=cpc
                    ↑
           Separates parameters

❌ WRONG (in value, unencoded):
?utm_campaign=buy&save
              ↑
     Breaks parameter parsing

✅ RIGHT (in value, encoded):
?utm_campaign=buy%26save

= (Equals)

✅ CORRECT USAGE (key-value separator):
?utm_source=facebook
           ↑
    Assigns value to key

❌ WRONG (in value, unencoded):
?utm_campaign=2+2=4
                ↑
       Confuses parser

✅ RIGHT (in value, encoded):
?utm_campaign=2%2B2%3D4

? (Question Mark)

✅ CORRECT USAGE (query start):
https://site.com?utm_source=google
               ↑
         Starts query string

❌ WRONG (in value, unencoded):
?utm_campaign=what?
                 ↑
       Breaks URL structure

✅ RIGHT (in value, encoded):
?utm_campaign=what%3F

# (Hash/Fragment)

✅ CORRECT USAGE (fragment):
?utm_source=facebook#section
                    ↑
           Marks fragment

❌ WRONG (in value, unencoded):
?utm_campaign=#1offer
              ↑
      Terminates query string

✅ RIGHT (in value, encoded):
?utm_campaign=%231offer

😰 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

Unsafe Characters (Always Encode)

These characters should be encoded even though they might work unencoded:

CharacterEncodedWhy Unsafe
<%3CHTML/XML conflict
>%3EHTML/XML conflict
"%22Attribute delimiter
'%27String delimiter
|%7CReserved
\%5CPath separator
^%5EReserved
[%5BReserved
]%5DReserved
{%7BReserved
}%7DReserved
`%60Reserved

Safe Characters (No Encoding Needed)

These can be used directly in UTM parameters:

SAFE: A-Z a-z 0-9 - _ . ~

Examples:
✅ utm_campaign=spring-sale-2024
✅ utm_source=my_email_list
✅ utm_medium=social.paid
✅ utm_content=banner~1

No encoding required

Common Scenarios

Scenario 1: Campaign Names with Spaces

Original: "Spring Sale 2024"

❌ WRONG:
utm_campaign=Spring Sale 2024

✅ RIGHT Option 1 (encode):
utm_campaign=Spring%20Sale%202024

✅ RIGHT Option 2 (underscores):
utm_campaign=Spring_Sale_2024

✅ RIGHT Option 3 (hyphens):
utm_campaign=spring-sale-2024

Scenario 2: Promotional Copy with &

Original: "Buy 1 & Get 1 Free"

❌ WRONG:
utm_campaign=Buy 1 & Get 1 Free

✅ RIGHT Option 1 (encode):
utm_campaign=Buy%201%20%26%20Get%201%20Free

✅ RIGHT Option 2 (replace):
utm_campaign=Buy-1-and-Get-1-Free

✅ RIGHT Option 3 (abbreviate):
utm_campaign=buy1get1free

Scenario 3: Percentages

Original: "20% Off"

❌ WRONG:
utm_campaign=20% Off

✅ RIGHT Option 1 (encode):
utm_campaign=20%25%20Off

✅ RIGHT Option 2 (replace):
utm_campaign=20pct-off

✅ RIGHT Option 3 (remove):
utm_campaign=20off

Scenario 4: Questions/Exclamations

Original: "Why wait?"

❌ WRONG:
utm_campaign=Why wait?

✅ RIGHT Option 1 (encode):
utm_campaign=Why%20wait%3F

✅ RIGHT Option 2 (remove):
utm_campaign=why-wait

Scenario 5: Dates with Slashes

Original: "2024/01/15"

❌ WRONG:
utm_content=2024/01/15

✅ RIGHT Option 1 (encode):
utm_content=2024%2F01%2F15

✅ RIGHT Option 2 (replace):
utm_content=2024-01-15

✅ RIGHT Option 3 (remove):
utm_content=20240115

Scenario 6: Email Addresses

Original: "user@example.com"

❌ PROBLEMATIC:
utm_content=user@example.com

✅ RIGHT Option 1 (encode):
utm_content=user%40example.com

✅ RIGHT Option 2 (hash):
utm_content=user_12ab34cd
(hash or anonymize for privacy)

Encoding Methods

JavaScript: encodeURIComponent()

// ✅ RECOMMENDED for UTM values
const value = "Spring Sale & Save 20%!";
const encoded = encodeURIComponent(value);
console.log(encoded);
// Spring%20Sale%20%26%20Save%2020%25!
 
// Build URL
const url = `https://site.com?utm_campaign=${"{"}{"{"}encoded{"}"}{"}"}}`;

JavaScript: URLSearchParams

// ✅ AUTOMATIC encoding
const params = new URLSearchParams();
params.set('utm_campaign', 'Spring Sale & Save 20%!');
 
const url = `https://site.com?${params.toString()}`;
console.log(url);
// https://site.com?utm_campaign=Spring+Sale+%26+Save+20%25%21
 
// Note: + for spaces is valid alternative to %20

Python: urllib.parse.quote()

from urllib.parse import quote
 
value = "Spring Sale & Save 20%!"
encoded = quote(value)
print(encoded)
# Spring%20Sale%20%26%20Save%2020%25%21
 
# Build URL
url = f"https://site.com?utm_campaign=`{"{"}{"{"}encoded{"}"}{"}"}}`"

PHP: urlencode() / rawurlencode()

$value = "Spring Sale & Save 20%!";
 
// urlencode() - uses + for spaces
$encoded1 = urlencode($value);
echo $encoded1;
// Spring+Sale+%26+Save+20%25%21
 
// rawurlencode() - uses %20 for spaces
$encoded2 = rawurlencode($value);
echo $encoded2;
// Spring%20Sale%20%26%20Save%2020%25%21
 
// Recommendation: Use rawurlencode() for UTMs

Validation Script

function validateSpecialCharacters(url) {
    const issues = [];
 
    try {
        const urlObj = new URL(url);
        const params = urlObj.searchParams;
        const rawQuery = url.split('?')[1] || '';
 
        // Characters that MUST be encoded in values
        const mustEncode = {
            ' ': '%20',
            '&': '%26',
            '=': '%3D',
            '?': '%3F',
            '#': '%23',
            '%': '%25',
            '+': '%2B' // If used as literal +, not space
        };
 
        // Check each UTM parameter
        ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'].forEach(key => {
            const value = params.get(key);
 
            if (value) {
                // Check raw query string for unencoded characters
                const paramPattern = new RegExp(`${"{"}{"{"}key{"}"}{"}"}}=([^&]*)`);
                const match = rawQuery.match(paramPattern);
 
                if (match) {
                    const rawValue = match[1];
 
                    // Check each special character
                    Object.entries(mustEncode).forEach(([char, encoded]) => {
                        if (rawValue.includes(char) && !rawValue.includes(encoded)) {
                            issues.push({
                                parameter: key,
                                character: char,
                                encoding: encoded,
                                message: `Contains unencoded "${"{"}{"{"}char{"}"}{"}"}}" - should be "${"{"}{"{"}encoded{"}"}{"}"}}"`
                            });
                        }
                    });
                }
            }
        });
 
    } catch (error) {
        issues.push({ error: 'Invalid URL', message: error.message });
    }
 
    return {
        valid: issues.length === 0,
        issues
    };
}
 
// Usage
const url = 'site.com?utm_campaign=spring sale&save';
const result = validateSpecialCharacters(url);
 
if (!result.valid) {
    console.log('❌ Issues found:');
    result.issues.forEach(issue => {
        console.log(`  ${issue.parameter}: ${issue.message}`);
    });
}

Quick Reference Table

Character Encoding Quick Lookup

Character  |  Encoded  |  Use Case
-----------|-----------|---------------------------
(space)    |  %20      |  "spring sale" → spring%20sale
&          |  %26      |  "save&win" → save%26win
=          |  %3D      |  "2+2=4" → 2%2B2%3D4
?          |  %3F      |  "what?" → what%3F
#          |  %23      |  "#1" → %231
%          |  %25      |  "20%" → 20%25
+          |  %2B      |  "C++" → C%2B%2B
/          |  %2F      |  "web/mobile" → web%2Fmobile
:          |  %3A      |  "sale:2024" → sale%3A2024
;          |  %3B      |  "a;b" → a%3Bb
<          |  %3C      |  "a<b" → a%3Cb
>          |  %3E      |  "a>b" → a%3Eb
"          |  %22      |  "say\"hi\"" → say%22hi%22
'          |  %27      |  "it's" → it%27s
|          |  %7C      |  "a|b" → a%7Cb
\          |  %5C      |  "a\b" → a%5Cb
^          |  %5E      |  "a^b" → a%5Eb
[          |  %5B      |  "a[b]" → a%5Bb%5D
]          |  %5D      |  "a[b]" → a%5Bb%5D
{          |  %7B      |  "a`{"{"}{"{"}b{"}"}{"}"}}`" → a%7Bb%7D
}          |  %7D      |  "a`{"{"}{"{"}b{"}"}{"}"}}`" → a%7Bb%7D
`          |  %60      |  "a`b" → a%60b

Testing Process

// Test character encoding
function testEncoding(value) {
    console.log('Original:', value);
    console.log('Encoded:', encodeURIComponent(value));
 
    // Build and test URL
    const url = `https://site.com?utm_campaign=${encodeURIComponent(value)}`;
    console.log('URL:', url);
 
    // Verify decoding
    const decoded = new URL(url).searchParams.get('utm_campaign');
    console.log('Decoded:', decoded);
    console.log('Match:', value === decoded ? '✅' : '❌');
}
 
// Test various special characters
testEncoding('Spring Sale & Save 20%!');
testEncoding('Buy 1 + Get 1 = 2 Free?');
testEncoding('Category: Electronics / Computers');
testEncoding('#1 Best Seller');

✅ 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

Should I encode safe characters (A-Z, 0-9, -, _)?

No. These don't need encoding and encoding them makes URLs less readable.

What's the difference between %20 and + for spaces?

Both are valid. %20 is more explicit. + is shorter but can be ambiguous if you need literal + sign. Use %20 for consistency.

Can I use emoji in UTM parameters?

Technically yes (they'll be encoded), but not recommended. Use ASCII characters for better compatibility and readability.

Do all platforms handle encoded characters correctly?

Most do, but test. Some older systems may have issues with heavily encoded URLs.

Conclusion

Special characters in UTM parameters must be encoded to prevent tracking errors.

Key Rules:

  1. Reserved characters (&, =, ?, #, %, +) MUST be encoded in values
  2. Use encodeURIComponent() to automatically encode all special characters
  3. Safe characters (A-Z, 0-9, -, _, ., ~) don't need encoding
  4. Test encoded URLs across platforms before launch

When in doubt: encode. Better safe than corrupted data.


Technical Reference: Ampersand Not Encoded Validation Rule