URL Encoding (Percent-Encoding), Explained

By Udit Agarwal · Updated October 17, 2026

URL encoding, also called percent-encoding, replaces each character that is not allowed in a URL, or that would be mistaken for part of its structure, with a % followed by two hexadecimal digits for each of its bytes. A space becomes %20, & becomes %26 and é becomes %C3%A9. It lets a value such as a search term, a file name or a redirect address travel inside a URL without breaking it.

Why URLs need encoding

A URL is built from a limited set of ASCII characters, and several of them do structural work: : ends the scheme, / separates path segments, ? starts the query, & and = separate query parameters, and # starts the fragment. If your data contains those characters, the receiver cannot tell data from structure.

Take a search for rock & roll. Pasted raw into ?q=rock & roll, the & splits the query in two: q gets only rock and a space, and roll becomes a second, empty parameter. Encoded, the query becomes ?q=rock%20%26%20roll, which decodes to exactly one parameter with the right value. A # is even more destructive: in ?q=C#sharp everything after the # becomes the fragment, which browsers never send to the server, so the server receives q=C.

RFC 3986, the URI standard, defines the fix. Each byte to be escaped becomes % plus two hex digits, so %25 is the percent sign itself. Hex digits are case-insensitive (%3a and %3A mean the same), and the RFC recommends uppercase.

Reserved and unreserved characters

RFC 3986 sorts characters into groups:

Group Characters When to encode
Unreserved A–Z a–z 0–9 - . _ ~ Never needed
Reserved, general delimiters : / ? # [ ] @ When they are data, not structure
Reserved, sub-delimiters ! $ & ' ( ) * + , ; = When they are data, not structure
Everything else spaces, ", <, >, %, {, }, non-ASCII text Always

The same character can be structure in one place and data in another. The / between path segments and the & between query parameters must stay as they are, while a / inside a file name or an & inside a search term has to be encoded. That is why you encode the individual pieces of a URL rather than the finished URL.

How non-ASCII characters become several escapes

Percent-encoding works on bytes, so text outside ASCII is first converted to UTF-8, which modern browsers use and RFC 3986 recommends. Each byte then gets its own %XX:

Character UTF-8 bytes Encoded
é C3 A9 %C3%A9
€ E2 82 AC %E2%82%AC
日 E6 97 A5 %E6%97%A5
😀 F0 9F 98 80 %F0%9F%98%80

A single code point can therefore become up to four escapes, 12 characters long, and emoji built from several code points produce longer runs. Decoding reverses the process and must reassemble valid UTF-8. A lone %E9, which is how old Latin-1 systems and the deprecated JavaScript escape() function write é, is not valid UTF-8, so decodeURIComponent('%E9') throws an error.

Domain names follow a different rule. An internationalized domain is converted to Punycode rather than percent-encoded, so münchen.de becomes xn--mnchen-3ya.de. The Punycode Converter converts in both directions.

A space: %20 or +?

Both are correct, in different places. In percent-encoding as RFC 3986 defines it, a space is %20, and that is always safe. HTML forms, however, submit data as application/x-www-form-urlencoded, a format in which a space becomes + and a literal plus becomes %2B. The browser's URLSearchParams follows the same format:

new URLSearchParams({ q: 'a b&c' }).toString()   // "q=a+b%26c"
decodeURIComponent('a+b')                        // "a+b", the plus is kept

The trouble starts when the two conventions meet. A query string read as form data turns every unencoded + into a space: ?q=C++ arrives as C followed by two spaces, and ?email=user+tag@example.com arrives as user tag@example.com. The fix is to encode the plus as %2B, which encodeURIComponent does (C%2B%2B, user%2Btag%40example.com). In the other direction, if you decode form data with decodeURIComponent, replace + with a space first.

encodeURIComponent vs encodeURI

JavaScript has two encoding functions with different jobs. encodeURIComponent is for one piece of data, such as a query value or a path segment, and escapes everything except A–Z a–z 0–9 - _ . ! ~ * ' ( ). encodeURI is for a complete URL you want to keep working, so it leaves the reserved characters ; / ? : @ & = + $ , # alone:

Input encodeURIComponent encodeURI
hello world hello%20world hello%20world
a&b=c a%26b%3Dc a&b=c
https://example.com/a b?q=1 https%3A%2F%2Fexample.com%2Fa%20b%3Fq%3D1 https://example.com/a%20b?q=1
é %C3%A9 %C3%A9

Because encodeURI keeps &, = and #, it cannot protect a value that contains them: encodeURI('https://example.com/search?q=rock&roll') returns the input unchanged, and roll still becomes a separate parameter. The reliable pattern is to build URLs from parts and pass each part through encodeURIComponent, or to let the URL and URLSearchParams APIs do the encoding.

One detail: encodeURIComponent leaves ! ' ( ) * unescaped, although RFC 3986 reserves them. Browsers and servers accept this, but some request-signing schemes, such as OAuth 1.0, expect those five to be encoded too.

Double encoding and other common bugs

Double encoding happens when an already-encoded value is encoded again. The % of %20 becomes %25, so a b turns into a%20b and then a%2520b, and the server, decoding once, sees the literal text a%20b. A %25 followed by two hex digits in a URL usually means something was encoded twice. Encode each value exactly once, at the moment you build the URL, and decode values taken from another URL before reusing them.

Other bugs that come up again and again:

  • Encoding the whole URL with encodeURIComponent, which produces https%3A%2F%2F…, a string that is no longer a URL.
  • Not encoding user input at all, so an &, # or + in a name or search term breaks the query.
  • Mixing character encodings, such as Latin-1 escapes like %E9 arriving at a UTF-8 decoder.
  • Decoding twice, which turns %2520 into a space instead of %20.

Decoding safely

Decode once, at the point where you parse the URL. Most web frameworks hand you query parameters already decoded, and decoding them again is how double-decoding bugs begin. Malformed input is common, since a lone %, 100%, %ZZ or %E9 all make decodeURIComponent throw a URIError, so wrap the call in error handling.

Decoding can also be a security issue. Attackers double-encode payloads to slip past filters: %252e%252e%252f contains no dots or slashes, but decoded twice it becomes ../, the classic path-traversal step. Check paths, redirect targets and anything you match with a regular expression after the final decode, never before.

Encode and decode with ArrayKit

The URL Encode / Decode tool has two modes. Encode runs your input through encodeURIComponent, so it treats the input as a single component: it escapes /, ?, :, &, =, # and spaces (as %20) and turns non-ASCII text into UTF-8 escapes. Decode runs decodeURIComponent, leaves + as it is, and shows an error for malformed sequences such as a lone % or %E9. Encode values one at a time, not whole addresses. It works in your browser, and what you paste is not uploaded.

For whole URLs, other tools fit better. The URL Parser splits a URL into protocol, host, port, path and fragment and lists the query parameters already decoded. The URL Builder adds UTM and custom parameters through the browser's URL API, so spaces in values come out as +. Query String to JSON reads + as a space, the way form data is read. And the Slug Generator avoids the problem for page addresses altogether: Crème Brûlée becomes creme-brulee instead of cr%C3%A8me-br%C3%BBl%C3%A9e.

Frequently asked questions

What is URL encoding? URL encoding, or percent-encoding, replaces characters that are unsafe or structural in a URL with a percent sign and two hex digits per byte. A space becomes %20 and an ampersand %26. It is defined in RFC 3986.

Why is a space %20 in some URLs and + in others? %20 is the standard percent-encoding of a space and works anywhere in a URL. The plus sign comes from the HTML form format, application/x-www-form-urlencoded, and only means a space inside query strings decoded as form data.

What is the difference between encodeURI and encodeURIComponent? encodeURIComponent encodes a single value and escapes reserved characters such as /, ?, & and =. encodeURI is meant for a whole URL and leaves those characters alone, so it cannot protect a value that contains them. Use encodeURIComponent for each piece of data.

What does %2520 mean in a URL? It is a double-encoded space. The space was encoded to %20, then the percent sign was encoded again to %25. Somewhere, a value that was already encoded went through an encoder a second time.

Which characters never need to be URL encoded? The unreserved characters of RFC 3986: the letters A to Z and a to z, the digits 0 to 9, hyphen, period, underscore and tilde. Everything else is either reserved for URL structure or must always be encoded.

Why does decoding fail with "URI malformed"? The input contains a percent sign that is not followed by two hex digits, or escapes that do not form valid UTF-8, such as a Latin-1 %E9. Fix or remove the broken sequence, or encode a literal percent sign as %25.

Is URL encoding a form of encryption? No. Anyone can decode it, and it hides nothing. It only makes data safe to carry inside a URL, much as Base64 makes binary data safe to carry as text.

Keep going

Encoder / Décoder URL

More guides

  • Base64 Encoding, Explained — Base64 explained: how it turns binary into text, the alphabet and padding, the URL-safe variant, size overhead, use cases, and why it isn't encryption.
  • ZIP Files, Explained — What a ZIP file is, how ZIP compression works, why photos and videos barely shrink, how to unzip safely on any device, and how ZIP compares with 7z and RAR.
  • UPI QR Codes, Explained — How a UPI QR code works: the upi://pay link and its pa, pn, am, cu and tn fields, static vs fixed-amount codes, checking the payee name, and print sizes.
  • QR Codes: Error Correction, Size and Logos, Explained — How QR error correction works, how large a logo can be, what size to print, which colors scan reliably, and when to use PNG or SVG so your code always scans.
  • Linux File Permissions and chmod, Explained — What chmod 755, 644 and 777 mean: owner, group and others, read, write and execute, octal and symbolic modes, umask, setuid, setgid and the sticky bit.
  • DMS and Decimal Degrees, Explained — How to convert DMS to decimal degrees and back, how DD, DMS and DDM differ, how many decimal places you need, and which formats Google Maps and GPS accept.

All Encode / Decode tools · All guides