URL Encoding and Decoding, Explained
June 4, 2026 · 4 min read
URLs can only safely contain a limited set of characters. The moment you need to put a space, an ampersand, a slash, or a non-English letter into a query string, you have to encode it — and getting this wrong is behind a surprising number of "why is my API call dropping half its parameters?" bugs. This guide explains what percent-encoding actually is, the difference between encodeURIComponent and encodeURI, and the pitfalls that trip people up.
What percent-encoding is and why it exists
A URL is supposed to be a short, portable string that any browser, proxy, or server can parse the same way. To make that work, the URL spec reserves certain characters for structure — / separates path segments, ? starts the query, & separates parameters, # starts the fragment, and so on. Other characters (spaces, quotes, most non-ASCII text) aren't safe to drop in raw at all.
Percent-encoding (also called URL-encoding) solves this by replacing an unsafe or reserved character with a % followed by its byte value in hexadecimal. A space becomes %20, an ampersand becomes %26, and a forward slash becomes %2F. Non-ASCII characters are first encoded as UTF-8 bytes, then each byte is percent-encoded — so an accented e (e-acute) becomes %C3%A9.
Reserved vs. unsafe characters
It helps to keep two categories straight:
- Reserved characters have a structural meaning in a URL:
: / ? # [ ] @ ! $ & ' ( ) * + , ; =. They're legal in a URL, but only in the right place. A&between parameters is fine; a&inside a value will be misread as a separator unless you encode it. - Unsafe characters have no business in a raw URL at all — spaces,
<,>,",{,}, and anything outside ASCII. These must always be encoded.
The unreserved characters — A–Z, a–z, 0–9, and - _ . ~ — never need encoding and are left alone by every correct encoder.
encodeURIComponent vs. encodeURI
JavaScript gives you two built-in functions, and picking the wrong one is the classic mistake.
encodeURIComponentencodes a single piece of a URL — one query value, one path segment. It escapes reserved characters like/,?,&, and=, because inside a value those are just data.encodeURIencodes a whole URL and deliberately leaves the structural characters (/ ? & = #) intact, so it won't break a URL you've already assembled.
const q = "tea & coffee/100%";
encodeURIComponent(q);
// "tea%20%26%20coffee%2F100%25" -- safe to use as a value
encodeURI("https://example.com/search?q=" + q);
// "https://example.com/search?q=tea%20&%20coffee/100%25"
// note the & and / survived -- this query is now broken
The rule of thumb: use encodeURIComponent for the pieces (each key and each value), then join them yourself with ?, &, and =. Reach for encodeURI only in the rare case where you have a complete, trusted URL that merely contains spaces or non-ASCII text and you want to tidy it up.
Decoding
Decoding reverses the process: decodeURIComponent turns %26 back into & and %20 back into a space. If you're inspecting a redirect URL, a Location header, or a logged request, decoding makes it human-readable. Just remember that decoding a string that was double-encoded (see below) only peels off one layer.
Common pitfalls
- Space as
%20vs.+. In the path, a space is always%20. In aapplication/x-www-form-urlencodedquery string (classic HTML form submissions), a space is historically a+. The two are not interchangeable everywhere —decodeURIComponentwill not turn+back into a space, so if a value came from form encoding you may need to replace+with a space first. - Double-encoding. Encoding an already-encoded string turns every
%into%25, so%20becomes%2520. This usually happens when a value passes through two layers that both "helpfully" encode it. The fix is to encode exactly once, at the boundary where you build the URL. - Encoding the whole URL vs. a single value. Running
encodeURIComponentover an entire URL escapes the:and/and produces a string that's no longer a usable URL. Encode the values, not the structure. - Forgetting non-ASCII is UTF-8 first. If your encoder and decoder disagree on the byte encoding, accented and CJK characters come back as garbage.
It runs in your browser
URLs frequently carry sensitive material — session tokens, signed callback URLs, API keys in query strings (which you should avoid, but they exist). The URL Encoder and URL Parser here do all the work in your browser: the encoding, decoding, and parsing happen in JavaScript on your own machine, with nothing sent to a server. For anything that touches a real request, local-only is the right default.
Try it
Encode or decode a value with the free URL Encoder — no signup, and nothing leaves your browser. Want to break a full URL into its scheme, host, path, and query parameters? Use the URL Parser.