URL Encoding Explained (Without the Guesswork)
What percent-encoding really is, when to reach for encodeURI versus encodeURIComponent, and the small mistakes that quietly break links and API calls.
Why URLs need encoding at all
A URL is a piece of text with a very strict grammar. It is built from a small, fixed alphabet of characters that browsers, servers, proxies and caches all agree on. When you want to put something into a URL that falls outside that alphabet — a space, an accented letter, an ampersand inside a value, a slash that is part of data rather than part of the path — you cannot simply drop it in. It has to be percent-encoded: replaced by a % followed by the two-digit hexadecimal code of each byte. A space becomes %20, an ampersand becomes %26, and the letter é becomes %C3%A9 in UTF-8.
This matters because an un-encoded character can change the meaning of a URL entirely. An ampersand that should be part of a company name (Ben & Jerry's) would otherwise be read as the separator between two query parameters. A # in a value would be treated as the start of a fragment. Encoding removes that ambiguity so the data survives the trip from your page to the server and back. You can try any example in theURL Encoder / Decoderas you read along.
Reserved, unreserved, and everything else
The specification that governs URLs (RFC 3986) splits characters into a few groups. Unreserved characters are always safe and are never encoded: the letters A–Z and a–z, the digits 0–9, and the four marks - _ . ~. Reserved characters have a structural job — they delimit the parts of a URL — and must be encoded when they appear inside data rather than as delimiters. Everything else (spaces, most punctuation, and any non-ASCII character) must always be encoded.
| Group | Characters | Encoded inside data? |
|---|---|---|
| Unreserved | A-Z a-z 0-9 - _ . ~ | Never |
| Reserved (delimiters) | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | Yes, when part of a value |
| Other | space, é, 中, emoji, most punctuation | Always |
encodeURI vs encodeURIComponent
JavaScript gives you two encoding functions, and choosing the wrong one is the single most common URL-encoding bug. The difference is simply how much they escape.encodeURIComponent assumes you are encoding one piece of a URL — a single query value, a path segment, a fragment — so it escapes the reserved delimiters too. encodeURI assumes you are encoding a whole, already-structured URL, so it deliberately leaves : / ? # & = alone to avoid destroying the address.
| Input | encodeURI | encodeURIComponent |
|---|---|---|
| https://x.com/a b | https://x.com/a%20b | https%3A%2F%2Fx.com%2Fa%20b |
| a&b=c | a&b=c | a%26b%3Dc |
The rule of thumb: encode each value with encodeURIComponent, then assemble the URL. Reach for encodeURI only when you already have a complete URL string that merely contains a stray space or accented character. In the tool above, the "Treat as a whole URL" checkbox switches between the two so you can see the difference on your own input.
Query strings and the space that becomes a plus
The query string — everything after the ? — is a list of key=value pairs joined by &. Both keys and values must be encoded, because a literal = or & inside a value would otherwise be misread as structure. So the search term fish & chips has to become fish%20%26%20chips (or fish+%26+chips in form style) before it can sit safely in ?q=.
This is where the notorious plus-sign confusion lives. Two different rules collide: modern URI encoding turns a space into %20, while the older application/x-www-form-urlencoded format used by HTML form submissions turns a space into +. Both are valid in a query string, and a correct decoder has to treat + as a space only there. That is exactly what the tool's query-parameter table does: it splits the pairs, converts + back to a space, and decodes each value so you can read what a link is really carrying.
Decoding — and why it sometimes fails
Decoding reverses the process: every %XX sequence is turned back into the byte it represents, and the bytes are interpreted as UTF-8 text. Most of the time this is invisible and instant. But decoding can throw an error, and it is worth understanding why. A decoder expects every % to be followed by exactly two hexadecimal digits (0–9, A–F). If it meets a lone %, an incomplete %A, or a byte sequence that is not valid UTF-8, it raises a URIError rather than guessing.
In practice this usually means one of three things: the text was never encoded in the first place and just happens to contain a percent sign (for example a "50% off" label); the string was truncated somewhere and lost half of a %XX pair; or the text was encoded with a different character set. The fix is almost always to check the source of the string rather than to force the decode. The tool surfaces a plain-English message instead of a cryptic exception so you can spot which case you are in.
The double-encoding trap
A subtle and frustrating bug is double encoding: encoding a string that was already encoded. If a b becomes a%20b, encoding that again turns the % itself into %25, giving a%2520b. When the server decodes once, it gets a%20b back — a literal percent-twenty in the data, not a space. Users see mangled text like hello%20world displayed on the page.
Double encoding typically happens when a value passes through two layers that each "helpfully" encode it — for instance a client that encodes a redirect target and a framework that encodes the whole URL again. The cure is to encode exactly once, at the point where you build the URL, and to decode exactly once when you read it. If you ever see a stray %25 where you expected a symbol, suspect double encoding first.
Real-world examples
- Search links: a "share this search" button must encode the query so
?q=survives punctuation.encodeURIComponent("c# tutorials")givesc%23%20tutorials; without it the#would cut the URL short. - Redirect parameters: a login page that returns you to
?next=/account?tab=billingmust encode the entirenextvalue, because it contains its own?and=. - API requests: a filter like
name=O'Brien & Sonssent as a query parameter has to be component-encoded so the apostrophe, space and ampersand do not break the request. - mailto and tel links: a pre-filled email subject with spaces and punctuation is encoded so
mailto:?subject=Meeting%20notesopens correctly.
Quick reference
| Situation | Use |
|---|---|
| Encoding one query value or path segment | encodeURIComponent |
| Fixing a whole URL with a space or accent | encodeURI |
| Reading an encoded value back | decodeURIComponent |
| A space in a form query value | + or %20 (decode both) |
Percent-encoding, byte by byte
To really understand what is happening, it helps to know that percent-encoding works on bytes, not on letters. When a character needs escaping, it is first turned into its raw bytes using a character encoding — on the modern web, always UTF-8 — and then each byte is written as a percent sign followed by that byte's two-digit hexadecimal value. For plain ASCII characters this is one byte and therefore one %XX: a space is byte 0x20, so it becomes %20; an ampersand is 0x26, so %26.
Non-ASCII characters are where it gets interesting. The letter é is Unicode code point U+00E9, which UTF-8 stores as the two bytes C3 A9 — so it encodes to %C3%A9, two percent-groups for one visible character. A full emoji like 😀 is four UTF-8 bytes and encodes to %F0%9F%98%80. This is exactly why a decoder has to reassemble the bytes and interpret them as UTF-8 to get the original text back, and why a mismatched character set produces garbled results. The tool handles all of this for you, which is how accented letters and emoji round-trip cleanly.
The same job in every language
URL encoding is such a universal need that every server language has a built-in function for it — though they differ in one important detail: how they handle a space. Functions built for the older HTML-form style encode a space as +, while functions built for general URLs use %20. Knowing which is which prevents a lot of confusion.
| Language | Function | Space becomes |
|---|---|---|
| JavaScript | encodeURIComponent() | %20 |
| Python | urllib.parse.quote() | %20 |
| PHP | rawurlencode() | %20 |
| PHP (form style) | urlencode() | + |
| Java | URLEncoder.encode() | + |
The practical lesson is to be consistent about what you encode with and to decode with the matching expectation — treating + as a space only inside a query string, and literally everywhere else. Because this browser tool follows the URL standard (%20 for spaces) while still decoding + as a space in query parameters, it interoperates cleanly with all of these.
A complete worked example
Suppose you are building a "share this" button that opens a pre-filled post with some text and a link back to the page. You want the shared text to be Ben & Jerry's: 50% off today! and the URL to be https://shop.example/deals?ref=share. If you drop those straight into the sharing URL, disaster: the & in the text ends the parameter early, the % is read as a broken escape, and the ? and & in your link are misinterpreted as more parameters.
The fix is to encode each value with encodeURIComponent before assembling the URL. The text becomes Ben%20%26%20Jerry's%3A%2050%25%20off%20today! and the link becomes https%3A%2F%2Fshop.example%2Fdeals%3Fref%3Dshare. Slot those encoded strings into ?text=…&url=… and every character now travels safely, with the receiving service decoding each parameter back to your exact intended text. Paste either value into the tool with "Encode" selected to watch this transformation happen — and note how it stays untouched under "whole URL" mode, which is the wrong choice here precisely because these are individual values, not a complete address.
An encoding checklist
- Encode values, assemble after. Escape each query value or path segment with
encodeURIComponent, then build the URL — never the other way around. - Encode exactly once. If you see a stray
%25, you have double-encoded; remove one layer. - Use UTF-8 everywhere. Consistent character encoding is what keeps accented letters and emoji intact.
- Decode once, at the right place. Read a value back a single time when you consume it, and treat
+as a space only in the query string. - Never put secrets in a URL. Encoding is not encryption — URLs are logged and cached, so keep tokens and personal data out of them where you can.
Not the only encoding in town
Percent-encoding is one member of a family of "make this safe for that context" encodings, and mixing them up is a common source of bugs. It helps to know what each is for. URL encoding makes text safe inside a URL. HTML entity encoding makes text safe inside HTML — turning < into < so a stray angle bracket in your content is not read as a tag (this is also the front line against cross-site scripting). Base64 does something different again: it makes binary data safe to sit inside text at all, at the cost of about a third more size.
| Encoding | Makes text safe for… | Space becomes |
|---|---|---|
| URL / percent | A URL or query string | %20 |
| HTML entities | HTML content | unchanged |
| Base64 | Embedding binary in text | n/a |
The reason this matters is that a value often passes through several of these contexts on its journey, and each needs the right treatment at the right moment. A search term typed by a user might be URL-encoded to put it in a link, then HTML-encoded to display it safely on the results page. Applying the wrong one — or the right one in the wrong place — produces either broken output or a security hole. Percent-encoding is specifically the tool for the URL leg of that journey, and nothing else.
URL encoding looks fiddly, but it comes down to two decisions: encode each value (not the whole address) with encodeURIComponent, and encode exactly once. Get those right and links, redirects and API calls just work. Paste any string into theURL Encoder / Decoderto see the encoded and decoded forms side by side, complete with a breakdown of every query parameter — all computed privately in your browser.