URL Encoding Explained: Percent-Encoding and Special Characters

URLs can only safely contain a limited set of ASCII characters. When you need to include spaces, special characters, or Unicode in a URL, you need percent-encoding. This guide explains exactly how it works and when to use encodeURI vs encodeURIComponent.

Why URLs Need Encoding

The URL specification (RFC 3986) restricts URLs to a safe subset of ASCII characters. Characters outside this set — spaces, Unicode letters, many punctuation marks — would be ambiguous or break URL parsing. For example, a space in a URL looks identical to the end of the URL in many contexts, and & separates query parameters, so it can't appear unencoded as a parameter value.

Percent-encoding (also called URL encoding) is the mechanism for safely including any character in a URL by representing it as % followed by its two-digit hexadecimal ASCII code.

The Percent-Encoding Mechanism

Each unsafe character is replaced with %XX, where XX is the uppercase hexadecimal representation of the character's byte value in UTF-8:

CharacterEncodedReason
Space%20Not allowed in URLs
#%23Fragment delimiter
%%25Encoding prefix itself
&%26Query param separator
+%2BMeans space in form encoding
=%3DKey-value separator
?%3FQuery string start
/%2FPath separator
é (U+00E9)%C3%A9Non-ASCII (UTF-8: 2 bytes)
€ (U+20AC)%E2%82%ACNon-ASCII (UTF-8: 3 bytes)

Reserved vs Unreserved Characters

RFC 3986 defines two important categories:

Unreserved characters — safe anywhere in a URL, never need encoding:

A-Z  a-z  0-9  -  _  .  ~

Reserved characters — have special meaning in URL syntax. They must be percent-encoded when used as data (not as structural delimiters):

:  /  ?  #  [  ]  @  !  $  &  '  (  )  *  +  ,  ;  =

Everything else — spaces, non-ASCII Unicode, control characters — must always be encoded.

encodeURI vs encodeURIComponent

JavaScript provides two encoding functions, and choosing the wrong one is a common source of bugs:

encodeURI(url)

Encodes a complete URL. It leaves reserved characters unencoded because they are structural — ://, /, ?, &, =, and # all have specific meaning in a full URL.

encodeURI("https://example.com/search?q=hello world&lang=en")
// → "https://example.com/search?q=hello%20world&lang=en"
// Note: & and = are left intact (they're structural)

encodeURIComponent(value)

Encodes a single value for use inside a URL component. It encodes reserved characters — including /, ?, #, &, and = — because those characters would break the URL structure if they appeared unencoded in a parameter value.

encodeURIComponent("price=10&currency=USD/EUR")
// → "price%3D10%26currency%3DUSD%2FEUR"
// Note: = / & all encoded (they'd break query parsing unencoded)

// Building a URL safely:
const query = encodeURIComponent(userInput);
const url = `https://api.example.com/search?q=${query}`;

Rule of thumb: use encodeURIComponent for individual values you're inserting into a URL. Use encodeURI only if you have a complete URL string and just need to make it safe without breaking its structure.

Query Strings and Form Data

HTML form submissions encode data using the application/x-www-form-urlencoded format, which has a quirk: spaces are encoded as + rather than %20. This applies only to query string values in form submissions — in URL paths, + is a literal plus sign.

When processing form data on the server, use a form-aware decoder (like PHP's $_GET, Python's urllib.parse.parse_qs, or Express's req.query) rather than a raw URL decoder, so + is correctly converted to a space.

Common Mistakes

Double encoding

Encoding an already-encoded string encodes the % sign itself: hello%20world becomes hello%2520world. Decode once to recover the original. Avoid double encoding by not encoding values that are already encoded.

Using encodeURI for parameter values

encodeURI does not encode & and =, so a parameter value containing those characters will corrupt the query string. Always use encodeURIComponent for individual values.

Forgetting that % must be encoded

If your data contains literal percent signs (e.g. "50% off"), they must be encoded as %25 before inserting into a URL. Both encodeURI and encodeURIComponent handle this automatically.

Encode and Decode URLs Online

Need to quickly encode a URL or decode a percent-encoded string? The URL Encoder on DevEssentials supports both encodeURIComponent and encodeURI modes — all client-side, nothing sent to any server.


Ready to encode or decode a URL? Open the URL Encoder →

Frequently Asked Questions

What is the difference between %20 and + for spaces in URLs?

%20 is the standard percent-encoding for a space character per RFC 3986. It is safe everywhere in a URL — in path segments, query strings, and fragments.

+ is part of the HTML form encoding format (application/x-www-form-urlencoded). When a browser submits a GET form, spaces in field values are encoded as +, not %20. A + only means 'space' in a query string context when decoded by a form parser. In a URL path segment, + is a literal plus sign.

Rule of thumb: use %20 for spaces in paths and URIs; be aware that + in query strings may mean space depending on how the server decodes it.

Which characters must be percent-encoded in a URL?

RFC 3986 defines two categories. Unreserved characters are safe anywhere and never need encoding: A–Z, a–z, 0–9, hyphen (-), underscore (_), period (.), and tilde (~).

Reserved characters have special meaning in URL syntax: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. They must be percent-encoded when used as data (not as structural delimiters). Everything else — non-ASCII Unicode, spaces, control characters — must also be percent-encoded.

What is the difference between encodeURI and encodeURIComponent?

encodeURI(url) encodes a complete URL. It leaves reserved characters (: / ? # @ etc.) unencoded because they are part of the URL structure. Use this when you have a full URL string that you want to make safe without breaking its structure.

encodeURIComponent(value) encodes a single value intended for use inside a URL component (typically a query parameter value or path segment). It encodes reserved characters like /, ?, #, &, and = because those characters would break the URL structure if they appeared unencoded inside a parameter value. Use this for individual values you're building into a URL.

What is double encoding and how do I avoid it?

Double encoding happens when an already-encoded string gets encoded again. The percent sign % itself gets encoded to %25, so %20 (space) becomes %2520 (literal '%20' instead of a space).

This often happens when encoding a URL that already contains encoded values, or when a framework automatically encodes values that you've already encoded manually. To avoid it: encode values exactly once before building the URL, and don't encode a URL that has already been constructed.

How does URL encoding handle Unicode characters?

Unicode characters are first converted to their UTF-8 byte sequence, then each byte is percent-encoded. For example, the é character (U+00E9) is represented as two bytes in UTF-8: 0xC3 0xA9, which encodes to %C3%A9 in a URL.

This is why a single Unicode character can produce multiple percent-encoded sequences. The decodeURIComponent() function in JavaScript automatically converts these UTF-8 sequences back to Unicode characters.