How to URL-encode query strings safely
July 31, 2026 · 9 min read
A query string is just text glued onto a URL with ? and &, which means any space, slash, or ampersand inside a value collides with the characters that give the URL its structure. A search term like news & weather pasted raw into ?q=news & weather doesn't fail loudly - it silently truncates at the space, or the server reads weather as a second, unrelated parameter.
Percent-encoding (RFC 3986) exists to solve exactly this: escape anything that isn't safe to appear literally in a URL, and leave everything else untouched.
Why raw text breaks URLs
Reserved characters - : / ? # [ ] @ ! $ & ' ( ) * + , ; = - and the plain space all carry structural meaning somewhere in a URL. A space in a query value can get interpreted as the end of the URL by some parsers; an unescaped & starts a new parameter; an unescaped # starts a fragment and everything after it silently never reaches the server at all.
This is why a URL copied out of a chat app sometimes "works" and the same URL pasted into a different context breaks - different consumers are more or less lenient about unescaped characters, but none of them are required to guess your intent correctly.
What percent-encoding actually does
Percent-encoding replaces each unsafe byte with % followed by its two-digit hex value. A space becomes %20, & becomes %26, / becomes %2F. Everything else - letters, digits, and a small set of always-safe characters - passes through unchanged, so the result stays mostly readable.
encodeURIComponent("news & weather"); // "news%20%26%20weather"
decodeURIComponent("news%20%26%20weather"); // "news & weather"
Encoding a component vs a whole URL
JavaScript gives you two different functions on purpose. encodeURIComponent escapes everything that isn't safe inside a single value - including /, ?, and & - which is what you want for a query parameter, a path segment, or a redirect target you're embedding inside another URL. encodeURI escapes far less, because it assumes you're encoding an entire URL and want to preserve its structural characters like / and ?.
The most common bug is using the wrong one: run a full URL through encodeURIComponent and you'll mangle the scheme and slashes into %3A%2F%2F; run a single query value through encodeURI and an embedded & will slip through unescaped, quietly breaking the next parameter.
URL encoding vs Base64
These solve different problems and aren't interchangeable. Base64 re-encodes arbitrary bytes into a fixed 64-character alphabet - the whole string changes shape, and every character costs the same regardless of input. Percent-encoding only escapes the handful of characters that are actually unsafe in a URL and leaves the rest as plain, readable text.
If you need to embed binary data (an image, a signed token) in a URL, Base64URL - Base64 with +// swapped for -/_ and padding stripped - is the right tool, since raw Base64's + and / are themselves unsafe in URLs. For plain text query values, percent-encoding is simpler and keeps the value legible in logs and browser history.
Common gotchas
Double-encoding is the most frequent real-world bug: a value gets encodeURIComponent'd once by your code and again by a framework or proxy, turning %20 into %2520. If a decoded value still has stray %XX sequences in it, that's usually the tell.
The other frequent surprise: + means a literal plus sign to encodeURIComponent/decodeURIComponent, but inside an application/x-www-form-urlencoded body (classic HTML form submissions), + means a space instead. Mixing up which encoding rules apply to query strings versus form bodies is a common source of "the space turned into a plus sign" bugs.
FAQ
- Does URL encoding hide or protect sensitive data?
- No. Percent-encoding is purely a compatibility transform, fully reversible by anyone - it is not encryption or obfuscation.
- Why does my decoded string still have %20 in it?
- The value was likely encoded twice. Decode it more than once, or fix the double-encoding at the source.
- Should I encode the whole URL or just the query value?
- Just the value. Encoding an entire URL with encodeURIComponent breaks its own structure by escaping the scheme and slashes.