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, so the value arrives truncated
=%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 and a table of decoded query parameters.

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