tooldura

Developer Tools

URL Encoding: encodeURI vs encodeURIComponent, and the Plus Sign

T
tooldura editorial
7 min readUpdated August 5, 2026Open tool →

A search query with an ampersand in it truncates. A redirect URL passed as a parameter loses everything after its own question mark. A filename with a hash sign becomes a fragment. All three are the same bug, and all three come from not encoding a value before putting it inside a URL.

Reserved, Unreserved, and Everything Else

RFC 3986 splits characters into groups, and the split is the whole basis of the rules.

Unreserved characters never need encoding: A-Z, a-z, 0-9, and the four punctuation marks `-`, `.`, `_`, `~`. These mean the same thing everywhere in a URL.

Reserved characters have structural jobs. `?` starts the query string. `#` starts the fragment. `&` separates parameters. `=` separates a key from its value. `/` separates path segments. `:` separates the scheme. When you want one of these as data rather than structure, it must be percent-encoded.

Everything else, including spaces, non-ASCII letters and most symbols, has to be encoded because it either has no defined meaning or is unsafe in transit.

Percent-encoding replaces a character with `%` followed by its byte value in hexadecimal. A space becomes `%20`. An ampersand becomes `%26`. Characters outside ASCII are encoded as their UTF-8 bytes, so `é` becomes `%C3%A9`, two bytes and therefore two escapes.

Characters You Will Encode Most Often

The right column is what happens if you leave the character raw inside a query parameter.

CharacterEncodedWhat breaks if you skip it
space%20Truncation, or conversion to + depending on the parser
&%26The value is split; everything after it becomes a new parameter
?%3FInterpreted as the start of a nested query string
#%23Everything after it is treated as a fragment and never sent to the server
=%3DAmbiguous key/value boundary; some parsers keep only the first part
/%2FRead as a path separator; breaks slugs and encoded paths
+%2BDecoded as a space by form parsers; a classic email address bug
%%25The next two characters are read as a hex escape

Why JavaScript Has Two Functions

`encodeURI` and `encodeURIComponent` are not alternatives. They encode different amounts on purpose, and picking the wrong one is the most common URL bug in JavaScript.

`encodeURI` is for a complete URL. It leaves reserved characters alone, because in a whole URL those characters are doing their structural job. Running it on `https://example.com/search?q=hello world` gives you a working URL with the space fixed and the `?` and `=` intact.

`encodeURIComponent` is for a single value going into a URL. It encodes reserved characters too, because inside a parameter they are data rather than structure. This is the one you want when building a query string.

The rule that covers almost every case: use `encodeURIComponent` on each key and each value, then join them yourself with `&` and `=`. Reach for `encodeURI` only when you have a URL that is already assembled and merely needs cleaning up, which is rarer than it sounds.

Both leave `-`, `.`, `_` and `~` alone. `encodeURIComponent` also leaves `!`, `'`, `(`, `)` and `*` unencoded, which is technically valid but occasionally trips up strict server-side parsers, particularly in older OAuth implementations.

The plus sign is a space, except when it is not

In `application/x-www-form-urlencoded` data, which is what HTML forms send and what most query strings are parsed as, `+` means a space. In a URL path, `+` is a literal plus. This inconsistency is why an email address like `user+tag@example.com` arrives as `user tag@example.com` when passed through a query string unencoded. Always encode a literal plus as `%2B`.

Double Encoding and How to Spot It

Encode a string twice and the percent signs from the first pass get encoded in the second. A space becomes `%20`, then the `%` becomes `%25`, giving `%2520`.

Seeing `%2520` in a log or an address bar tells you exactly what happened: something encoded a value that had already been encoded. The usual culprits are a framework that encodes automatically combined with code that also encodes manually, or a redirect URL passed through two layers of routing.

The fix is never to decode twice as a workaround. It is to find which layer is encoding and stop the other one. Decoding twice appears to work until a user submits a value containing a literal `%25`, at which point the extra decode corrupts real data.

The reverse problem exists too. A value that was never encoded but contains a `%` followed by two hex digits will be silently decoded into something else. A password of `100%25off` decodes to `100%off`, and the authentication fails for reasons the logs will not explain.

Encode or decode a URL

Both directions, with full UTF-8 support. Nothing leaves your browser.

Open URL Encoder →

Rules That Prevent Most URL Bugs

Four habits eliminate the large majority of encoding problems in practice.

1

Encode values, never whole URLs

Build the URL from encoded pieces rather than encoding the finished string. Use URLSearchParams in JavaScript and it handles the escaping and joining for you.

2

Encode once, at the boundary

Decide which layer owns encoding and make every other layer pass values through untouched. Most double-encoding comes from two layers each being helpful.

3

Store decoded, transmit encoded

What goes in the database should be the real value. Encoding is a transport concern, so apply it when constructing a URL and reverse it on arrival.

4

Never encode a URL you are about to store as a link

Encoding an href that is already valid turns its slashes and colons into escapes, producing a link that resolves relative to the current page. This is the cause of the mysterious 404 on a URL that looks correct.

Frequently Asked Questions

Related Tools

Keep Reading