Route first: what are you encoding
Three different jobs hide behind "URL encoding", and picking the wrong mode is how accidents start:
| What you're encoding | The mode | Why |
|---|---|---|
A parameter value (content going after ?key=) |
Component (encodeURIComponent) | The & = ? # inside the value must be escaped, or & gets parsed as the next parameter's separator |
A complete URL (already has ? and & structure; just legalize spaces and Chinese) |
URI (encodeURI) | Structure characters preserved; only illegal characters encoded |
| A form submission body (application/x-www-form-urlencoded) | Form mode | Spaces encode as + instead of %20 — a legacy convention, more below |
All three modes (the URL Encoder Decoder supports them; switch and compare):
input: a=hello world&next=你
component: a%3Dhello%20world%26next%3D%E4%BD%A0 (& = escaped — treated as data)
uri: a=hello%20world&next=%E4%BD%A0 (structure kept, only illegal chars encoded)
form: a%3Dhello+world%26next%3D%E4%BD%A0 (spaces become +, form convention)
One-line memory hook: encoding "data" takes component, encoding an "address" takes uri, going through a "form" takes form.
Why parameters must be encoded
Skipping encoding isn't "sometimes works" — it's a buried mine. Four accident scenes:
&truncates parameters: an&inside a value reads as the next parameter's start —q=a&barrives as two parameters, the first one cut;#cuts the query: everything after#is a fragment identifier and never reaches the server — a#in the value makes the rest vanish;- Spaces and Chinese: some browsers encode them as a fallback, but "some" means unpredictable behavior that differs across clients;
%itself: a raw%in the value becomes an escape introducer on the decoding side, and%GGthrows immediately.
The spec's attitude is simple: only a subset of ASCII is guaranteed safe in a URL; everything else is percent-encoded. %E4%BD%A0 is "你" — UTF-8 bytes E4 BD A0, each written as % plus two hex digits. Not mojibake, not encryption — a legal spelling of the same content. One Chinese character mapping to three %XX groups is the UTF-8 signature.
+ versus %20: two faces of a legacy
Spaces have two encodings: %20 (percent-encoding, the standards path) and + (form encoding, an HTML-era convention that stuck).
The accident's typical shape: a value containing a literal + — phone number +86, formula a+b — gets decoded by the server under form rules, and + returns as a space: +8613800000000 becomes 8613800000000. Sneakier still is the signature case: the client encodes spaces as %20, the server normalizes under form rules to +, the signature strings diverge, verification fails — you suspect the secret key, but it's spaces fighting.
Diagnosis: check the request's Content-Type — application/x-www-form-urlencoded means the + convention; JSON bodies leave spaces alone. Fix: encode literal + as %2B on the sending side; standardize both ends on one space representation. A tool with a form mode lets you verify both ends behave consistently.
Troubleshooting checklist: decode failures and double encoding
- Invalid URI error: anything after
%that isn't two hex digits (%GG, a lone%, a truncated%E4%BD) makesdecodeURIComponentthrow. Search for lone%to detect truncation first; - Double encoding:
%encoded to%25, so%E4becomes%25E4— a string starting with%25deserves one more decode layer. The cause is usually a framework encoding once and the code encoding again; - Still mojibake after one decode: count the
%XXgroups per Chinese character — three groups is normal UTF-8; remaining garbage means another encoding layer (Base64 inside URL, for instance — strip layer by layer); - URL structure broke after decoding: decoding a whole URL in component mode restores
&=as data and wrecks the parameter structure — the decoding mode must match how it was encoded.
The truth: encoding solves transport, not semantics
Percent-encoding guarantees "this content can cross the URL channel safely". It does not solve parameter semantics: duplicate keys (which a=1&a=2 wins is the server's business), array serialization formats (a[]=1 vs a=1,2 vs a=1&a=2 are framework conventions), representations of empty and null — no amount of encoding-tool fiddling answers those; check your framework's docs. The tool's territory is the encoding layer's debugging: getting this string encoded right, decoded right, and seen for what it is.
Frequently Asked Questions
What's the difference between encodeURIComponent and encodeURI, and when do I use each?
The difference is their attitude toward URL structure characters: encodeURIComponent escapes & = ? # / entirely — it assumes you're encoding "a piece of data"; encodeURI preserves those characters — it assumes you're encoding "a complete URL". Encoding a parameter value must use encodeURIComponent, or the & inside your value becomes the server's next parameter separator; encoding a fully structured URL is encodeURI's job. Reversed: the former cuts the URL in half, the latter truncates your value.
Why did the Chinese in my URL parameter turn into something like %E4%BD%A0?
That's percent-encoding: the URL specification allows only a subset of ASCII to appear literally. The character first becomes UTF-8 bytes — "你" is E4 BD A0 — and each byte is written as a percent sign plus two hex digits. It's neither encryption nor corruption, just another legal spelling of the same content; a decoder restores it in one step. The tell for UTF-8: one Chinese character maps to three %XX groups.
Why did my plus signs turn into spaces after decoding?
It's the legacy of form encoding: HTML forms encode spaces as + instead of %20, and the convention stuck. A value containing a literal + (phone number +86) gets decoded under form rules, and + returns as a space — signature mismatches and corrupted data trace back here. Check the Content-Type: x-www-form-urlencoded means the + convention applies; JSON bodies don't. The fix: encode literal + as %2B, or standardize both ends on %20.
Why does decoding throw an Invalid URI error?
decodeURIComponent throws on illegal percent sequences — anything after % that isn't two hex digits (%GG, a lone %, a truncated sequence). Three common causes: wrong layer count (double-encoded content decoded only once), a raw % character never encoded to %25, or a string truncated during copy-paste. Troubleshoot in order: search for lone % to detect truncation, then decode once more to detect double encoding.