Let me guess — you've had a bug where a URL with spaces or special characters broke something. Maybe a search query with & in it caused weird behavior. Or you saw %20 in a URL and wondered what that was about. Let's clear all of this up.
Why URLs Need Encoding
URLs can only contain a limited set of characters. The RFC 3986 spec defines "unreserved" characters that are always safe: A-Z, a-z, 0-9, -, _, ., and ~. Everything else — spaces, &, =, ?, non-ASCII characters like ñ or 日本語 — needs to be percent-encoded.
How Percent-Encoding Works
It's straightforward: take the character's byte value in UTF-8, and represent each byte as % followed by two hex digits.
Examples:
- Space →
%20 &→%26=→%3Dé→%C3%A9(two bytes in UTF-8)日→%E6%97%A5(three bytes in UTF-8)
So Hello World becomes Hello%20World in a URL path.
The #1 Bug: Double Encoding
This is the most common URL encoding mistake I see, and it's subtle. You encode a string, then pass it to a function that encodes it again. Now %20 (an already-encoded space) becomes %2520 because the % itself gets encoded.
Example of the problem:
The fix: encode values once, at the right level. Don't encode things that are already encoded.
Plus Signs vs %20 — Yes, It's Confusing
In URL query strings, spaces can be either + or %20. The + convention comes from HTML form encoding (application/x-www-form-urlencoded). In URL paths, only %20 is valid.
So https://example.com/hello world → path encodes to https://example.com/hello%20world
But https://example.com/search?q=hello world → query can be ?q=hello+world or ?q=hello%20world
encodeURI vs encodeURIComponent
JavaScript gives you two functions, and using the wrong one is a classic mistake:
encodeURI()— Encodes a full URL. It leaves:,/,?,#,&,=alone because they're structural parts of the URL.encodeURIComponent()— Encodes a URL component (like a query parameter value). It DOES encode:,/,?,#,&,=because those might be part of the data.
Rule of thumb: use encodeURIComponent() for values, never for entire URLs. The MDN docs explain the difference beautifully.
Other Languages
- Python:
urllib.parse.quote()for paths,urllib.parse.urlencode()for query strings — docs here - Java:
URLEncoder.encode()(uses+for spaces — watch out!) - PHP:
urlencode()for query strings,rawurlencode()for paths
Quick Tips
- Always encode parameter values, not entire URLs
- Decode once on the receiving end — don't pass encoded values through multiple layers
- Test with edge cases: spaces,
&,=,#, non-ASCII characters, and emoji
Building URLs Safely in JavaScript
The right way to construct URLs with query parameters is to use the built-in URL and URLSearchParams APIs. They handle all the encoding for you:
Notice how URLSearchParams uses + for spaces (HTML form encoding convention) and properly encodes the & in the value so it doesn't get confused with the & that separates parameters. This is much safer than string concatenation.
Common URL Encoding Pitfalls
1. Encoding the entire URL instead of just values. If you run encodeURIComponent() on a full URL, it will encode the ://, /, ?, and & characters — making the URL completely unusable. Only encode individual parameter values.
2. Forgetting to encode hash fragments. The # character in a URL starts a fragment identifier. If your data contains a # and you don't encode it, everything after it disappears from the server's perspective. The server never sees fragment identifiers — they're client-side only.
3. Not handling international domain names (IDN). Domain names with non-ASCII characters like example.日本 need special handling called Punycode encoding. This is separate from percent-encoding and applies only to the hostname portion of the URL.
4. Encoding spaces inconsistently. Some parts of your system might encode spaces as + and others as %20. This usually works fine for decoding, but it can cause issues with URL comparison and caching. Pick one convention and stick with it.
Percent-Encoding Quick Reference
| Character | Encoded | Why it needs encoding |
| Space | %20 or + | Not allowed in URLs |
& | %26 | Separates query parameters |
= | %3D | Separates key from value |
? | %3F | Starts query string |
# | %23 | Starts fragment identifier |
% | %25 | The escape character itself |
/ | %2F | Path separator |
@ | %40 | Used in userinfo section |
URL Encoding in Different Contexts
URL encoding isn't just for browser URLs. You'll encounter it in these common situations:
- API requests: Query parameters in REST API calls need proper encoding, especially if they contain user input
- Redirect URLs: When passing a return URL as a parameter (like
?redirect=https://...), the entire redirect URL needs to be encoded as a value - OAuth flows: OAuth callback URLs and state parameters are notoriously tricky because they involve multiple layers of URL encoding
- Deep links: Mobile deep links follow the same URL encoding rules, but some platforms have additional requirements
Debugging URL Encoding Issues
When something goes wrong with URL encoding, here's my debugging checklist:
1. Check for double encoding — Look for %25 in the URL, which means a % was encoded twice
2. Check the raw request — Use your browser's DevTools Network tab to see exactly what URL was sent, before the browser displays the decoded version
3. Compare encoded vs decoded — Paste the problematic URL into a decoder to see what it actually contains
4. Check server-side decoding — Some frameworks auto-decode URL parameters, and doing it manually on top of that causes issues
The Anatomy of a URL
Okay, let's take a step back. Before we get deeper into encoding, you need to actually understand what a URL is made of. I know, I know — you've been using URLs your entire career. But I bet you don't know all the parts by their official names. Most devs don't, and that's where encoding confusion starts.
According to RFC 3986, a URL has this structure:
Let me walk you through each piece:
- Scheme (
https,ftp,mailto) — The protocol. No encoding needed here, it's always ASCII letters. - Authority — This includes the optional
user:password@(which you should basically never use in 2026), the host (domain name or IP), and an optional port number. - Path (
/search/results) — The hierarchical part. Forward slashes/separate segments. Within each segment, you need to encode special characters, but you do NOT encode the slashes themselves. - Query (
?q=hello&lang=en) — Key-value pairs after the?. The&separates pairs,=separates keys from values. You encode the keys and values, but not the structural&and=. - Fragment (
#section-2) — The part after#. This one's interesting — it never gets sent to the server. It's purely client-side. But you still need to encode special characters within it.
Here's the thing that trips people up: different parts of the URL have different encoding rules. A / is perfectly fine in the path (it's a separator!) but needs to be encoded as %2F if it appears inside a query parameter value. An @ sign is fine in the authority section but should be encoded in the path. This is why one-size-fits-all encoding functions cause so many bugs.
Think of it like this: the URL is a sentence with grammar rules. The special characters are punctuation. You wouldn't encode a comma that's actually being used as a comma — you only encode a comma that's part of the data and might be confused for punctuation.
encodeURI vs encodeURIComponent: The JavaScript Minefield
Alright, this one drives me absolutely crazy because I see devs get it wrong ALL the time. JavaScript gives you two encoding functions, and they sound almost identical, but using the wrong one will ruin your day.
Let me lay it out clearly:
encodeURI() is designed for encoding a complete URL. It encodes unsafe characters but leaves the structural ones alone — things like :, /, ?, #, &, =, @. Because if you're encoding a full URL, you obviously don't want to break the URL structure.
encodeURIComponent() is designed for encoding a single value that goes inside a URL. It encodes EVERYTHING except letters, digits, and - _ . ~. This includes :, /, ?, #, &, = — because when these appear in a value, they're data, not structure.
Here's a comparison that makes it crystal clear:
| Character | encodeURI() | encodeURIComponent() |
: | : (unchanged) | %3A |
/ | / (unchanged) | %2F |
? | ? (unchanged) | %3F |
# | # (unchanged) | %23 |
& | & (unchanged) | %26 |
= | = (unchanged) | %3D |
@ | @ (unchanged) | %40 |
| Space | %20 | %20 |
é | %C3%A9 | %C3%A9 |
Now here's where it gets scary. Watch what happens when you use the wrong one:
And the opposite mistake is just as bad:
The golden rule: encodeURIComponent for values, encodeURI for full URLs. Or better yet, just use the URL API and let the browser handle it. Seriously, the URL and URLSearchParams classes exist for a reason. See the MDN encodeURIComponent docs and MDN encodeURI docs for the full details.
URL Encoding in Different Languages
JavaScript isn't the only language with confusing URL encoding functions. Every language has its own quirks, and I kid you not, some of them are even more confusing than JavaScript's pair.
Python — Actually pretty reasonable, once you find the right module:
Java — Here's where it gets weird. URLEncoder was designed for HTML form encoding, not general URL encoding. So spaces become + instead of %20:
C# — .NET actually gives you the right tools, but there are like five different methods and they all do slightly different things:
PHP — Oh PHP. Of course you have two functions with almost identical names that do slightly different things:
Go — Clean and sensible, as Go tends to be:
The takeaway? Every language handles the + vs %20 thing differently, and every language has at least one function that will surprise you. Always check the docs for whatever language you're working in. Don't assume it works the same as JavaScript.
Unicode in URLs: The Wild West
Okay so here's where it gets REALLY interesting. What happens when you put non-ASCII characters in a URL? Like, what if you want a URL with Japanese, Arabic, or — I kid you not — emoji?
The answer involves two completely different systems, and mixing them up is a classic mistake.
For the path and query parts: You use percent-encoding. Take the character's UTF-8 bytes and encode each one. So café becomes caf%C3%A9. Your browser does this automatically and usually shows you the pretty version in the address bar, but under the hood it's sending the percent-encoded version.
For domain names: Percent-encoding is NOT used. Instead, there's this wild system called Punycode. It converts Unicode domain names to ASCII-compatible strings that start with xn--.
Check this out:
café.com→xn--caf-dma.commünchen.de→xn--mnchen-3ya.de例え.jp→xn--r8jz45g.jp
Why two different systems? Because DNS (the system that turns domain names into IP addresses) was built in the 1980s and only supports ASCII. So they had to invent a way to cram Unicode into ASCII strings — and Punycode is that invention. The path and query parts of a URL, on the other hand, are handled by web servers that can deal with percent-encoded bytes.
There's actually a whole spec for Unicode in URLs called IRI (Internationalized Resource Identifiers) defined in RFC 3987. An IRI is basically a URL that allows Unicode characters directly. Browsers convert IRIs to URIs behind the scenes.
And yes, emoji domains exist. 💩.la is a real domain (or was at some point). It gets Punycode-encoded to xn--ls8h.la. I don't recommend using emoji domains for anything serious, but it's a fun proof that the system works.
One gotcha with Unicode in URLs: different Unicode representations of the "same" character. For example, é can be represented as a single codepoint (U+00E9) or as e + a combining accent mark (U+0065 + U+0301). These produce different percent-encoded strings! The WHATWG URL Standard recommends NFC normalization, but not all systems follow this consistently.
Double Encoding: The Bug That Haunts Your Dreams
I mentioned double encoding earlier, but it deserves its own deep-dive because this bug has probably wasted more collective developer hours than any other URL-related issue. Been there, done that — multiple times.
Here's the basic scenario:
What happened? When you encode hello%20world a second time, the % character gets encoded to %25. So %20 becomes %2520. The server sees the literal string %20 instead of a space.
This sounds obvious when I spell it out, but in real codebases it's incredibly sneaky. Here are the scenarios where double encoding bites you:
Proxy chains. You send a request to server A, which forwards it to server B. If both servers encode the URL, boom — double encoded. API gateways like Kong, AWS API Gateway, or nginx reverse proxies are common culprits.
Redirect chains. User goes to page A, gets redirected to page B with a ?returnUrl=... parameter, and page B redirects again with the return URL as a parameter. Each redirect might re-encode the URL. After three redirects, your URL is triple-encoded and completely mangled.
Framework "helpers." Some web frameworks automatically encode URL parameters. If you manually encode before passing to the framework, you get double encoding. I've seen this happen with Spring Boot, Express.js middleware, and Django's URL reversing.
How do you detect double encoding? Look for %25 in the URL. That's a percent sign that's been encoded, which usually means something was encoded twice. If you see %2520, that's a double-encoded space. If you see %253D, that's a double-encoded = sign.
How to fix it:
The best defense is to establish clear boundaries in your code: encode at the edges (right before sending an HTTP request, or right before constructing a URL to display), and pass raw, unencoded strings everywhere else internally.
URL Length Limits and What to Do About Them
Here's something that'll surprise you: the HTTP spec itself does NOT define a maximum URL length. RFC 3986 says URLs should be "of an unlimited length." But the real world disagrees.
Different components in the chain have their own limits, and the shortest one wins:
| Component | Max URL Length |
| Internet Explorer (RIP) | 2,083 characters |
| Chrome, Firefox, Safari | ~65,000+ characters |
| Apache (default) | 8,190 characters |
| Nginx (default) | 8,192 characters |
| IIS (default) | 16,384 characters |
| AWS ALB | 8,192 characters |
| Cloudflare | 32,768 characters |
That old 2,083-character limit from IE used to rule everything. Even though IE is basically dead now, some developers and tools still treat it as gospel. But in practice, most modern stacks can handle much longer URLs.
That said, just because you CAN make a 65,000-character URL doesn't mean you SHOULD. Here are some real reasons to keep URLs short:
- Server logs. Many logging systems truncate long URLs, which makes debugging a nightmare.
- Copy-paste. Users copy and share URLs. Super-long URLs break in emails, chat messages, and docs.
- SEO. Search engines generally recommend keeping URLs under 2,000 characters.
- Caching. Some CDN and proxy cache key limits are URL-based. Longer URLs = more cache misses.
So what do you do when your URL is getting too long? Glad you asked:
1. Use POST instead of GET. If you're sending a lot of data, put it in the request body. The request body has no practical size limit. This is the most common solution for complex search forms with many filters.
2. Use URL shorteners or reference IDs. Generate a short token that maps to the full set of parameters stored server-side. So instead of /search?filter1=abc&filter2=def&filter3=... you get /search/saved/abc123.
3. Compress your parameters. Some apps base64-encode a compressed JSON blob and put it in the URL. Not pretty, but it works. You'll see this in tools like Grafana dashboard URLs.
Form Encoding: application/x-www-form-urlencoded
Let's talk about the elephant in the room that makes URL encoding even more confusing: HTML form encoding. When you submit an HTML form with method="POST", the browser encodes the form data using a format called application/x-www-form-urlencoded. And this format is ALMOST the same as standard percent-encoding, but with one key difference that drives everyone crazy.
Spaces become + instead of %20.
That's it. That's the main difference. But oh boy, does it cause confusion.
Why does this difference exist? Historical reasons, of course. The HTML form encoding spec predates the URL encoding spec, and it used + for spaces because... well, someone thought it looked nicer in the '90s. And now we're stuck with it forever.
When you submit an HTML form, the browser sends a Content-Type: application/x-www-form-urlencoded header, and the server knows to interpret + as spaces. But if you're building URLs manually in JavaScript and you use + for spaces in the path portion, the server will see literal plus signs. Fun times.
Here's where it matters in practice:
And then there's multipart/form-data, which is a completely different beast. When your form includes file uploads (), the browser switches to multipart/form-data encoding, which uses boundaries to separate fields instead of & signs. It doesn't use URL encoding at all — each part has its own headers and body. If you've ever tried to manually parse a multipart request, you know the pain.
The practical advice: use URLSearchParams for query strings and form data. Use FormData for file uploads. Don't try to encode these things by hand unless you really, really have to.
Try It Yourself
Working with URLs that look wrong? Paste them into our URL Decoder to see the actual characters. Need to encode a value before putting it in a URL? The URL Encoder handles it instantly. And for breaking down complex URLs into their components, use the URL Parser to see each part clearly.