URL Encoding and Decoding — A Complete Developer Guide
Why URLs need percent-encoding, what each component requires, plus signs in query strings, and safe encode/decode workflows for APIs and forms.
By Vertex Solutions Editorial
A support ticket arrived with a screenshot: a search link showing q=hello%2520world and zero results. Someone ran encodeURIComponent twice. The server searched for the literal string hello%20world instead of hello world.
URL encoding looks trivial until plus signs, slashes, Unicode, and double-encoding collide. The rules are consistent once you know which part of the URL you're encoding.
Quick answer
A support ticket arrived with a screenshot: a search link showing q=hello%2520world and zero results. Someone ran encodeURIComponent twice. The server searched for the literal string hello%20world instead of hello world.
URL anatomy: encode the right piece
https://example.com:443/path/to/page?name=John+Doe&tag=c%2Fa#section
|_____| |_________|_|____________| |______________| |__|
scheme host port path query fragment
Each component has different reserved characters:
| Component | Encode? | Notes |
|-----------|---------|-------|
| Scheme | No | https is fixed |
| Host | Rarely | Internationalized domains use punycode |
| Path | Sometimes | / is delimiter, not encoded within path segments |
| Query values | Yes | &, =, +, spaces need care |
| Fragment | Sometimes | Not sent to server; still matters for client routing |
Encoding the entire URL as one string breaks structural characters. Encode individual parameter values, then assemble.
What percent-encoding does
Unsafe character → % + two hex digits of the UTF-8 byte:
| Character | Encoded |
|-----------|---------|
| space | %20 |
| & | %26 |
| = | %3D |
| + | %2B |
| / | %2F |
| ? | %3F |
| # | %23 |
| é | %C3%A9 (UTF-8 bytes) |
The name "percent-encoding" comes from the % prefix.
Use URL Encoder to inspect how specific strings transform.
encodeURI vs encodeURIComponent (JavaScript)
encodeURI — for full URLs; preserves :/@?#[] etc.
encodeURI("https://example.com/a b");
// https://example.com/a%20b
encodeURIComponent — for query parameter values; encodes everything including / and ?
encodeURIComponent("a/b?c");
// a%2Fb%3Fc
Rule of thumb: URIComponent for values, URI for whole URLs (rarely needed if you build URLs with URLSearchParams).
The plus sign confusion
In application/x-www-form-urlencoded (HTML forms):
- Space →
+
In URL paths and RFC 3986 strict encoding:
- Space →
%20
Decoders in most frameworks accept + as space in query values. Don't rely on + in paths.
When debugging "space works locally but not in production," check whether one layer decodes + and another doesn't.
URL Decoder shows both representations side by side.
Building query strings correctly
Wrong:
const url = `/search?q=${query}`; // breaks if query contains & or =
Right:
const params = new URLSearchParams({ q: query, page: "1" });
const url = `/search?${params.toString()}`;
URLSearchParams handles encoding per key and value.
Manual construction — encode each value with encodeURIComponent, join with &:
/search?q=hello%20world&tag=node%2Fjs
Decoding: once, at the right layer
Servers and frameworks decode query parameters automatically. Problems arise when:
- Client encodes
- Server decodes
- Application encodes again before storage
- Display decodes again
Each layer should encode outbound and decode inbound exactly once.
Use URL Decoder to verify what a string looks like after one decode pass.
Double-encoding: the silent killer
Original: 100% done
First encode: 100%25%20done
Second encode: 100%2525%2520done
Links shared through multiple systems (email → CRM → redirector) sometimes accumulate encoding layers. Symptom: search terms look right in the address bar but match wrong in the database.
Fix: Decode in a loop until stable, or compare against known double-encoding patterns (%25 for %).
Paths with special characters
File paths in URLs need segment-level encoding:
/files/2024/Q1 Report.pdf ❌ (spaces)
/files/2024/Q1%20Report.pdf ✓
Slashes inside a single path segment (filename with /) must encode the slash:
encodeURIComponent("a/b") → a%2Fb
But don't encode the slashes between path segments.
Unicode and international URLs
https://example.com/café — browsers display Unicode; wire format may use punycode for domains and percent-encoding for paths:
/caf%C3%A9
Always UTF-8 encode before percent-encoding bytes. Latin-1 assumptions break emoji and CJK.
Base64 in URLs: different tool
Putting binary in URLs sometimes uses Base64url (- and _ instead of + and /). That's alphabet substitution, not percent-encoding.
JWTs in URLs need Base64url, not encodeURIComponent on the whole token.
APIs and redirect URLs
OAuth redirect_uri parameters must match registered values exactly — encoding differences cause redirect_uri_mismatch errors.
Common mistakes:
- Trailing slash encoded vs not
httpvshttps- Query parameter order (some providers care)
Log the exact bytes sent, not the pretty-printed version.
HTML links vs JavaScript URLs
<a href="/search?q=hello world"> ❌ ambiguous
<a href="/search?q=hello%20world"> ✓
Template engines may auto-escape & in attributes — verify output in View Source, not rendered DOM alone.
Server-side decoding (conceptual)
Most frameworks provide:
decodeURIComponent(JavaScript)urllib.parse.unquote(Python)url.QueryUnescape(Go)
Plus form parsers that convert + to space in query values.
Never decode before authentication middleware if the raw URL is part of the signature (webhook verification, AWS SigV4).
Security considerations
- Open redirects — validate decoded redirect targets against an allowlist
- Log injection — decoded
%0Anewlines in parameters can break log formats - SSRF — encoded
http://127.0.0.1may bypass naive string filters; decode then validate - XSS in reflected parameters — encoding for URL context ≠ HTML context; escape at output layer too
Extracted values headed for JSON APIs should pass through JSON Formatter during debugging to spot embedded control characters.
Debugging checklist
| Symptom | Check |
|---------|-------|
| + appears as literal plus | Decoder didn't treat + as space in query |
| %20 visible in UI | Over-encoded or not decoded on display |
| Broken slashes in path | encodeURIComponent on full path instead of segments |
| Unicode mojibake | Wrong charset before percent-encoding |
| OAuth redirect fails | Byte-exact compare of encoded redirect_uri |
Framework-specific gotchas
Axios and fetch — params serializers differ. Axios may encode differently than URLSearchParams. When debugging partner integrations, log config.paramsSerializer output, not assumed defaults.
Ruby on Rails — CGI.escape vs ERB::Util.url_encode handle spaces differently (+ vs %20). Mixing helpers across a codebase causes subtle bugs in redirect URLs.
PHP — urlencode() vs rawurlencode() mirror the space-as-plus vs space-as-%20 split. RFC 3986 prefers rawurlencode for path segments.
Java — URLEncoder.encode uses + for spaces (application/x-www-form-urlencoded style). Use URI builder classes for RFC-compliant path encoding.
When stack traces show encoding at multiple layers, draw a diagram of encode/decode boundaries — framework middleware often decodes before your handler runs, and your handler encodes again before storage.
Internationalized domain names (IDN)
Hostnames with Unicode (münchen.de) use punycode in the wire format (xn--mnchen-3ya.de). That's separate from path/query percent-encoding but shows up in the same URLs developers copy from browsers. Don't manually encode punycode hostnames — use URL parser libraries that accept Unicode display form.
Related articles
- Understanding Base64 Encoding — Base64url vs percent-encoding
- Understanding Regular Expressions — parse log lines with encoded params
- JSON Formatting Guide — API payloads with encoded fields
Related tools
- URL Encoder — Encode strings for URL components
- URL Decoder — Decode percent-encoded values
- JSON Formatter — Inspect API request bodies
- Regex Tester — Parse query strings from logs
Key takeaways
- What is URL encoding: Percent-encoding replaces reserved or unsafe characters with a percent sign followed by two hexadecimal digits representing the character's byte value, like %20 for space.
- What is the difference between encodeURI and encodeURIComponent: encodeURI encodes a full URL but preserves structural characters like /, ?, and.
- Why do spaces sometimes appear as plus signs: HTML form submission (application/x-www-form-urlencoded) historically encoded spaces as +.
Conclusion
URL encoding translates characters that would break URL structure into %XX escape sequences. Encode each component at construction time, decode once at consumption, and know whether + means space in your context. Double-encoding and wrong encoder choice (encodeURI vs encodeURIComponent) cause most production link bugs — not missing libraries.
Frequently Asked Questions
Common questions answered to help you get the most from this tool.