URL encoding (also called percent-encoding) replaces characters that a URL can't safely carry with a % followed by two hex digits. A space becomes %20, an ampersand becomes %26, a question mark becomes %3F. Links break when a character that has a structural meaning in a URL — like &, ?, #, /, + or a space — appears inside a value where it was meant to be treated as plain text.
That single sentence explains about 90% of the "why is my link broken?" tickets on the internet. But the other 10% are the ones that eat your afternoon, so let's go properly through it.
What a URL is actually made of
Before you can understand why a link breaks, you need to see a URL the way a browser does. It isn't one long string of text. It's a structure with parts, and certain characters are the dividers between those parts.
https://toolnexin.com/blog/post?utm_source=x&utm_medium=cpc#section-2
Break that apart:
https://— the schemetoolnexin.com— the host/blog/post— the path, where/separates the segments?— signals "the query string starts here"utm_source=x— a key-value pair, where=separates key from value&— separates one parameter from the next#section-2— the fragment, which never even gets sent to the server
Now look at those dividers: /, ?, =, &, #. These are called reserved characters. They have a job. They're punctuation.
So what happens if your actual data contains one of them?
The core problem, in one example
Say you're tracking a campaign called summer sale & clearance. You build a link like this:
https://toolnexin.com/?utm_campaign=summer sale & clearance
Looks fine to you. To a browser, it reads:
- The campaign is
summer sale(it stops at the&) - Then there's a brand new parameter called
clearancewith no value - And the space might get mangled or silently truncated along the way
Your campaign name is destroyed. Your analytics splits one campaign into two. Nobody notices for three weeks.
The fix is encoding:
https://toolnexin.com/?utm_campaign=summer%20sale%20%26%20clearance
Now the & inside the value is %26, which the browser reads as "the literal character &, not a separator." The space is %20. The value arrives intact.
That's the whole concept. Encoding is how you tell a URL: this character is data, not punctuation.
How percent-encoding works
The mechanism is refreshingly simple. Take the character, look up its byte value, write it in hexadecimal, and stick a % in front.
The space character is byte 32, which is 20 in hex. So a space becomes %20.
Here are the ones you'll actually run into:
| Character | Encoded | Why it breaks things |
|---|---|---|
| (space) | %20 |
Gets truncated or turned into + |
& |
%26 |
Starts a new query parameter |
? |
%3F |
Starts the query string |
# |
%23 |
Starts the fragment — everything after is lost to the server |
/ |
%2F |
Separates path segments |
= |
%3D |
Separates key from value |
+ |
%2B |
Means "space" in a query string |
% |
%25 |
It's the escape character itself |
: |
%3A |
Separates scheme and port |
@ |
%40 |
Separates credentials from host |
Characters that are always safe and never need encoding: letters A-Z and a-z, digits 0-9, and the four characters -, ., _, ~. These are the unreserved set. Everything else is a candidate for trouble.
You can run any string through a URL encoder and decoder to see exactly what comes out, which is far faster than looking up a hex table every time.
The + versus %20 confusion
This one deserves its own section because it causes genuine, expensive bugs.
Historically, HTML forms encoded spaces as + rather than %20. That convention stuck. So in a query string, many systems treat + as a space.
Which leads to this classic disaster:
https://example.com/contact?phone=+919876543210
The server receives the phone number as " 919876543210" — with a leading space instead of a plus. The country code is gone. Your WhatsApp link doesn't work. Your form validation fails on a number the user typed perfectly.
The fix: a literal plus sign in a URL value must be encoded as %2B.
https://example.com/contact?phone=%2B919876543210
Rule of thumb: %20 is always safe. + is only sometimes safe. When in doubt, use %20.
Path encoding versus query encoding
Here's a subtlety that catches out even experienced developers: the rules are different depending on where in the URL you are.
A forward slash in the path is perfectly normal — it's supposed to be there:
https://toolnexin.com/blog/developer-tools
But a forward slash inside a parameter value must be encoded, or it'll be read as structure:
https://toolnexin.com/redirect?next=%2Fblog%2Fdeveloper-tools
This matters enormously in one very common scenario: passing a full URL as a parameter. Login redirects, OAuth callbacks, share links, tracking wrappers — they all do this.
Wrong:
https://example.com/login?next=https://example.com/dashboard?tab=billing
That has two ? characters and the browser has no idea what you meant. The ?tab=billing gets attached to the outer URL, not the inner one.
Right:
https://example.com/login?next=https%3A%2F%2Fexample.com%2Fdashboard%3Ftab%3Dbilling
The entire inner URL is encoded as a single opaque value. Every :, /, ? and = inside it is neutralised.
Double encoding: the bug that hurts
Since % itself has to be encoded as %25, you get a nasty trap.
Encode a space once: %20. Encode that result again: the % becomes %25, so you get %2520.
Now your user sees a filename like my%20report.pdf on the page, or the server hunts for a file literally called my%20report.pdf and returns a 404.
This happens when two parts of your stack both "helpfully" encode the same value. Your frontend encodes it, then your backend framework encodes it again on the way out. Or you build a link in a spreadsheet, encode it, then paste it into a tool that encodes it a second time.
How to spot it: if you ever see %25 in a URL and you didn't intend to include a literal percent sign, you have double encoding. Also watch for %2520, %2526, and %253A — those are %20, %26 and %3A that got hit twice.
How to fix it: decode once and check. A URL decoder is the fastest way to peel the layers and see what you've actually got.
What about non-English characters?
URLs were originally designed for plain ASCII. Anything outside that — accented letters, Hindi, Chinese, Arabic, emoji — gets converted to UTF-8 bytes first, and then each byte gets percent-encoded.
ébecomes%C3%A9(two bytes)नमस्तेbecomes a long chain of%triplets- A single emoji becomes four bytes, so twelve characters of encoding
This is why a Hindi or Chinese URL slug looks like alphabet soup when you copy it out of the address bar. The browser shows you the pretty version; the actual bytes are percent-encoded.
Worth knowing: the domain name part works differently. Non-ASCII domains use a system called Punycode, not percent-encoding, so भारत.com becomes something starting with xn--. Different mechanism, same underlying problem.
Real-world use cases where this bites
1. Campaign tracking that quietly falls apart
This is the most expensive one, because it destroys data silently. A UTM parameter with a space, an ampersand, or a # in it will fragment your reporting, and you often won't spot it until you're trying to reconcile a quarter's numbers.
If your campaign is Diwali Sale #1, that # truncates everything after it — the entire rest of your query string vanishes. The safe approach is to build links with a proper UTM builder that handles encoding for you, rather than hand-typing them into a spreadsheet.
This is also a very common root cause of traffic showing up where it shouldn't. If you've been fighting mystery buckets in analytics, our guide on GA4 unassigned traffic and how to fix it covers it, and the common UTM mistakes in GA4 post lists the encoding traps specifically.
2. Search boxes and filters
A user searches for C++ & Rust. Your site builds a URL like ?q=C++ & Rust. The + signs become spaces, the & starts a new parameter, and your search returns results for C with a mysterious empty filter attached.
Any user-supplied string going into a URL must be encoded. No exceptions. This is also the doorway to a category of security bugs, so it isn't just a cosmetic issue.
3. Filenames with spaces
Q3 Report Final v2.pdf uploaded to a CDN becomes Q3%20Report%20Final%20v2.pdf. That works, but it's ugly in the address bar and it's fragile — one system in the chain forgets to encode and the link 404s.
The pragmatic fix is upstream: use hyphens in filenames instead of spaces. q3-report-final-v2.pdf. Never think about it again.
4. QR codes
A QR code encodes a URL as a string. If that URL is malformed, the QR code faithfully encodes the malformed version, and every single person who scans it lands on a broken page — or worse, a truncated one that half works.
Always encode the URL properly before generating the code, and always scan-test the result yourself. If you're building one, the QR code generator is the place to paste an already-clean URL.
5. Share links and mailto links
Prefilled share links carry the message text as a parameter. That text almost always contains spaces, punctuation and sometimes emoji.
mailto:hi@example.com?subject=Q3%20Report&body=Hi%2C%0A%0APlease%20find%20it%20attached.
Note %0A — that's a newline. Yes, even line breaks get encoded.
If your shared link previews are coming out blank or mangled, encoding is one of the usual suspects. Our post on why your link preview isn't showing an image covers the broader diagnosis, and a link preview extractor will show you what a scraper actually sees when it fetches your URL.
6. API tokens and signatures
Authentication signatures are computed over an exact string. If your client encodes a parameter one way and the server encodes it another way, the signatures don't match and you get a 403 that makes no sense — because your credentials are perfectly correct.
This is the single most confusing encoding bug to debug, because nothing looks wrong. The usual culprit is + versus %2B inside a base64-encoded token, since base64 output frequently contains + and / characters. Which brings us to a related point.
URL encoding is not encryption (and not base64)
Worth stating plainly, because these get conflated constantly:
- URL encoding makes a string safe to travel in a URL. It's fully reversible by anyone and hides nothing.
- Base64 makes binary data representable as text. Also fully reversible, also hides nothing.
- Encryption makes data unreadable without a key. This is the only one that provides secrecy.
Putting %73%65%63%72%65%74 in a URL does not hide the word "secret." Anyone can decode it in one second. We wrote about this trap in base64 vs encryption, and the same warning applies here.
Amusingly, the two interact badly: base64 output contains +, / and =, all of which are reserved in URLs. So a base64 string dropped raw into a query parameter will often corrupt. Either URL-encode it, or use base64url (a variant that swaps + and / for - and _). If you're working with base64 regularly, the base64 encoder is handy for checking what you're actually dealing with.
How to encode in code
Quick reference, because the naming is genuinely confusing across languages.
JavaScript
encodeURIComponent("summer sale & clearance")
// "summer%20sale%20%26%20clearance" <- use this for parameter VALUES
encodeURI("https://site.com/my page")
// "https://site.com/my%20page" <- use this for a WHOLE url
The difference matters: encodeURI leaves &, ?, = and / alone because it assumes they're structure. encodeURIComponent encodes them, because it assumes you're passing data. For a single parameter value, you almost always want encodeURIComponent.
Python
from urllib.parse import quote, quote_plus
quote("a b&c") # 'a%20b%26c'
quote_plus("a b&c") # 'a+b%26c' <- space becomes +
PHP
rawurlencode("a b"); // "a%20b" <- RFC 3986, use this
urlencode("a b"); // "a+b" <- legacy form style
The pattern across all three: there are two functions, one uses %20 and one uses +, and the names give you almost no hint which is which. When you're unsure, encode the string in a URL encoder tool and compare against what your code produced.
Common mistakes checklist
- Hand-typing tracking links into a spreadsheet instead of building them properly
- Forgetting that a literal
+must be%2B - Encoding an already-encoded string and creating
%2520 - Using
encodeURIwhere you neededencodeURIComponent - Dropping a raw base64 token into a query parameter
- Assuming
#in a value is harmless (it silently discards everything after it) - Passing a full URL as a parameter without encoding it
- Using spaces in filenames and hoping the CDN sorts it out
Frequently asked questions
What is URL encoding? URL encoding, also called percent-encoding, converts characters that have a special meaning in a URL into a % followed by two hexadecimal digits. A space becomes %20 and an ampersand becomes %26, so the character is treated as data rather than as punctuation.
Why does %20 appear in my URL? %20 is the encoded form of a space character. It appears because URLs cannot safely carry literal spaces, so any space in a path or parameter is converted to %20.
What is the difference between %20 and +? Both can represent a space, but + only means a space inside a query string, following an old HTML form convention. %20 works everywhere. If you need a literal plus sign, encode it as %2B.
What does %2520 mean? It means your value was encoded twice. %20 (a space) had its % re-encoded to %25, producing %2520. Decode the URL once and re-encode it correctly.
Should I use encodeURI or encodeURIComponent? Use encodeURIComponent for individual parameter values, because it encodes reserved characters like & and =. Use encodeURI only when encoding an entire URL where the structural characters should be preserved.
Does URL encoding make my link secure? No. It is fully reversible by anyone and provides no secrecy at all. It makes a link valid, not private.
Do I need to encode non-English characters? Yes. Characters outside basic ASCII are converted to UTF-8 bytes and each byte is percent-encoded, which is why a Hindi or Chinese slug looks like a long chain of % codes when copied out of the address bar.
The takeaway
URL encoding isn't complicated once you see the underlying idea: a URL is a structure, certain characters are its punctuation, and encoding is how you say "this one is just text."
Get that, and the rest falls out naturally. Spaces break because they're not allowed. Ampersands break because they mean "next parameter." Hashes break because everything after them never reaches the server. Plus signs break because they used to mean space and nobody ever cleaned that up.
Don't hand-build links. Use a URL encoder and decoder when you're debugging one, a UTM builder when you're creating tracking links, and a URL shortener when the encoded result is too ugly to share. Ten seconds of encoding saves you a quarter of broken analytics.
