How to URL Encode a String (and When You Must)
A URL is a structured string, not free text. The ? starts the query, & separates parameters, = splits name from value, # begins the fragment. When your data contains those characters, the parser cannot tell your content from the address's own punctuation — and it always sides with the punctuation.
Percent-encoding is the fix: rewrite the problem characters so they carry no structural meaning. It is one of the four transport encodings covered in The Complete Guide to Text Encoding on the Web, and the one most likely to break a production integration quietly rather than loudly.
The rule
Take the character's byte value, write it in hexadecimal, prefix with %.
A space is byte 0x20, so it becomes %20. An ampersand is 0x26, so %26. A question mark is 0x3F, so %3F. Two hex digits, always uppercase by convention, always exactly one byte.
For anything outside ASCII, the character is first encoded to UTF-8 bytes, and then each byte gets its own escape. So é — UTF-8 bytes C3 A9 — becomes %C3%A9. A single emoji becomes four escapes. This is why the charset your system uses matters underneath the URL layer; encode as Latin-1 and é becomes %E9 instead, and any UTF-8 decoder on the other end will produce garbage.
Which characters need it
Three groups, and the distinction is worth keeping straight:
Unreserved — never encode. A-Z, a-z, 0-9, and the four characters -, ., _, ~. These are always safe. Encoding them is legal but pointless and makes URLs ugly.
Reserved — encode when they are data, not structure. : / ? # [ ] @ ! $ & ' ( ) * + , ; =. A / between path segments is a delimiter and stays raw. A / inside a filename is data and must become %2F. Same character, opposite treatment, determined entirely by role.
Everything else — always encode. Spaces, control characters, and every non-ASCII character.
The single most common production bug in this area is an unencoded & inside a parameter value. ?company=Ben & Jerry's gets parsed as a company parameter of Ben plus a stray parameter named Jerry's. The value is silently truncated, no error is raised, and the bug surfaces weeks later in a data quality report.
The %20 versus + nuance
This is the detail that catches nearly everyone.
In a URL path, a space is %20. Always. + is a literal plus sign there.
In a query string encoded as application/x-www-form-urlencoded — the default format for HTML form submissions and for most API clients posting form data — a space is traditionally written as +. This is a legacy convention from early HTML forms that never went away, and it is genuinely part of the spec for that media type.
So /search?q=hello+world and /search?q=hello%20world typically mean the same thing, and both differ from /files/hello+world.txt, where the plus is a real plus.
Two practical consequences:
- A literal plus in a query value must be
%2B. A phone number sent as?phone=+15551234567arrives asphone= 15551234567— the country-code plus becomes a space. This breaks phone fields, mathematical expressions, and Base64 payloads containing+. - Decoders must match encoders. A decoder that treats
+as a space will corrupt path data; one that does not will corrupt form data. Know which side you are on.
The Query String Parser / Builder makes this concrete — build a parameter list, see the assembled string, and confirm which convention your target expects.
Encode the parts, never the whole
This is the discipline that prevents most percent-encoding bugs.
Wrong:
encode("https://api.example.com/search?q=blue shoes&sort=price")
→ https%3A%2F%2Fapi.example.com%2Fsearch%3Fq%3Dblue%20shoes%26sort%3Dprice
The URL's own delimiters got escaped. The result is one meaningless string, not an address.
Right — encode each value, then assemble:
https://api.example.com/search?q=blue%20shoes&sort=price
In JavaScript this maps onto two functions that exist for exactly this split. encodeURIComponent() encodes reserved characters and is what you want for a parameter name or value. encodeURI() leaves delimiters alone and is only for tidying an entire URL you already trust. Reaching for encodeURI() on a parameter is the source of an enormous number of unencoded-ampersand bugs.
Our URL Encoder / Decoder works on a single value at a time deliberately, which mirrors the correct workflow: encode the value, then paste it into the URL you are assembling.
Double encoding
If a value passes through two layers that each encode it, the escapes themselves get escaped. % is byte 0x25, so:
"blue shoes" → blue%20shoes → blue%2520shoes
Decoding once gives you the literal string blue%20shoes, not blue shoes. Users see %20 printed on screen; searches for a name with an apostrophe return nothing; a redirect lands on a 404.
The tells are %25 appearing in a URL, or a decoded value that still contains percent escapes. The cause is nearly always two well-meaning layers — application code plus an HTTP client library, or a framework helper plus a manual call. Fix it by deciding which layer owns encoding and removing the other; do not compensate by decoding twice, which will break the moment a user legitimately types a %.
When a URL is misbehaving and you cannot see why, breaking it into components with the URL Parser usually makes the problem obvious — you can see immediately whether a delimiter landed inside a value.
Related escaping contexts
Percent-encoding is specific to URLs. Putting the same value into HTML requires a different escape — & in a query string is %26, but the same URL printed inside an href attribute needs &, which is why HTML entities and percent-encoding often stack on the same string. Getting the order right matters: percent-encode for the URL, then entity-encode for the markup.
For binary data destined for a URL, skip standard Base64 — its + and / both need escaping. Use Base64URL instead, which substitutes - and _ and passes through untouched. And because non-ASCII encoding depends entirely on the underlying charset, it is worth understanding how UTF-8 turns characters into bytes before debugging an accented-character URL.
A quick checklist
- Encode values individually, never assembled URLs.
- Use
encodeURIComponent(), notencodeURI(), for parameters. - Encode
+as%2Bin query values if you mean a literal plus. - Suspect double encoding whenever you see
%25. - Confirm UTF-8 is in play before debugging non-ASCII escapes.
Every encoder and parser linked here runs entirely in your browser. URLs frequently carry session tokens, signed parameters, and customer identifiers, so the value you are debugging is often something you would rather not send to an unfamiliar server — with client-side tools, you do not have to.
Frequently asked questions
What is URL encoding?
URL encoding, or percent-encoding, replaces characters that are unsafe or structurally meaningful in a URL with a percent sign followed by the character's byte value in two hex digits. A space becomes %20 because a space is byte 0x20.
Why is a space sometimes %20 and sometimes a plus sign?
In a URL path a space is always %20. In a query string sent as application/x-www-form-urlencoded — the default for HTML form submissions — a space is written as +. Both conventions exist, so decoders must know which one applies.
Which characters need to be encoded?
The unreserved set A-Z a-z 0-9 - . _ ~ never needs encoding. The reserved delimiters : / ? # [ ] @ ! $ & ' ( ) * + , ; = must be encoded when they appear inside a value rather than acting as delimiters, along with spaces and all non-ASCII characters.
How are accented characters and emoji encoded?
They are converted to UTF-8 bytes first, then each byte gets its own %XX escape. That is why é becomes %C3%A9 — one character, two bytes, two escapes. Emoji typically produce four escapes.
What does %2520 mean in a URL?
It is a double-encoded space. A space became %20, then the percent sign itself was encoded as %25, producing %2520. Decoding once yields the literal text %20 rather than a space, which means something encoded the value twice.
Should I encode the whole URL or just the parts?
Just the parts. Encode each path segment and each parameter name and value individually, then assemble them with / & and =. Encoding an assembled URL escapes its own delimiters and destroys the structure.
Do I need to encode a plus sign in a query value?
Yes. In form-encoded query strings a bare + is interpreted as a space, so a literal plus — in a phone number, for example — must be sent as %2B or it silently disappears.
Try the related tools
URL Encoder / Decoder
Encode or decode URL components and query parameters.
Base64URL Encode / Decode
URL-safe Base64 encoding and decoding without padding or special URL characters.
URL Parser & Breakdown
Deconstruct URLs into protocol, host, port, path, query parameters, and hash fragment.
Query String Parser / Builder
Parse query strings into key-value pairs or construct URL-encoded parameter strings.