Skip to content
ProfitTool
Back to all articles
Developer Tools
5 min read

URL Encoding Explained: What Are Percent-Encoded Characters?

Percent-encoding converts characters that cannot appear literally in a URL into %XX form. How it works, and when to use encodeURIComponent over encodeURI.

ProfitTool Team

Security & Dev

What URL Encoding Is

URLs may only contain a restricted set of ASCII characters. Anything outside that set — a space, an accented letter, an ampersand inside a value — has to be represented as one or more percent-encoded bytes: a % followed by two hexadecimal digits.

https://example.com/search?q=café latte&sort=price
https://example.com/search?q=caf%C3%A9%20latte&sort=price

The rules come from RFC 3986. Percent-encoding is often called URL encoding; they are the same thing.

Which Characters Are Safe

RFC 3986 divides characters into three groups, and the group determines whether encoding is required, forbidden, or a judgement call.

GroupCharactersEncoding
UnreservedA–Z a–z 0–9 - . _ ~Never encode — encoded and unencoded forms are equivalent
Reserved: / ? # [ ] @ ! $ & ' ( ) * + , ; =Encode when the character is data; leave it when it is structure
Everything elseSpaces, <, >, ", {, }, \, ^, backtick, all non-ASCIIAlways encode

The reserved group is where the thinking happens. / separates path segments, ? starts the query, & separates parameters, = joins a key to its value. Those same characters appearing inside a value must be encoded, or the parser will read them as structure.

# Wrong — the & inside the value ends the parameter early
?company=Smith & Jones&page=2

# Right
?company=Smith%20%26%20Jones&page=2

The Encoding Algorithm

For any character that needs encoding:

  1. Encode the character as UTF-8, producing one or more bytes.
  2. Write each byte as % plus two uppercase hex digits.

ASCII characters are one byte and produce one escape. Non-ASCII characters produce several:

CharacterUTF-8 bytesEncoded
space20%20
&26%26
#23%23
éC3 A9%C3%A9
€E2 82 AC%E2%82%AC
😀F0 9F 98 80%F0%9F%98%80

RFC 3986 specifies uppercase hex digits, though decoders accept either. Non-ASCII characters are always UTF-8 in modern URLs — this is what makes %C3%A9 and not some other byte sequence the correct encoding of é.

The Space Problem: %20 vs +

Spaces have two encodings, and both are correct in the right place.

EncodingDefined byWhere it applies
%20RFC 3986Anywhere in a URL — path, query, fragment
+HTML application/x-www-form-urlencodedQuery strings and form bodies only

The + form comes from HTML form submission, not from the URI specification. In a query string, most servers accept both. In a path segment, + is a literal plus sign and will not be decoded as a space.

/search?q=hello+world      → q = "hello world"    (form decoding)
/search?q=hello%20world    → q = "hello world"    (always)
/files/hello+world.txt     → filename contains a literal +
/files/hello%20world.txt   → filename contains a space

%20 is the safe default everywhere. Reach for + only when you are deliberately producing form-encoded data.

Encoding in Practice

JavaScript

Two functions, and choosing the wrong one is the most common URL bug in the language.

// encodeURI — for a COMPLETE URL. Leaves reserved characters alone
// so the URL keeps working.
encodeURI("https://example.com/my page?q=a&b=1");
// → "https://example.com/my%20page?q=a&b=1"

// encodeURIComponent — for a SINGLE VALUE. Escapes reserved
// characters so they cannot be mistaken for structure.
encodeURIComponent("Smith & Jones");
// → "Smith%20%26%20Jones"

Using encodeURI on a value leaves & and = intact, which lets a user-supplied value inject extra query parameters. Using encodeURIComponent on a whole URL escapes the :// and / separators and destroys it.

The rule: encodeURIComponent for each piece, encodeURI for the whole thing — and in practice, build URLs with URLSearchParams instead.

const url = new URL("https://example.com/search");
url.searchParams.set("q", "Smith & Jones");
url.searchParams.set("tag", "café");
url.toString();
// → "https://example.com/search?q=Smith+%26+Jones&tag=caf%C3%A9"

URLSearchParams encodes every value correctly and cannot be given the wrong function. Note that it uses + for spaces, since it produces form-encoded query strings — correct here, and correctly decoded by URLSearchParams on the other side.

One historical wrinkle: encodeURIComponent does not escape !, ', (, ) or *, which RFC 3986 lists as reserved sub-delimiters. It is almost never a problem, but strict parsers occasionally care:

const strict = (s) =>
  encodeURIComponent(s).replace(/[!'()*]/g, (c) =>
    "%" + c.charCodeAt(0).toString(16).toUpperCase());

Python

from urllib.parse import quote, quote_plus, urlencode, unquote

quote("Smith & Jones")            # 'Smith%20%26%20Jones'
quote("a/b")                      # 'a/b'      — '/' is safe by default
quote("a/b", safe="")             # 'a%2Fb'    — encode it as data
quote_plus("Smith & Jones")       # 'Smith+%26+Jones'  — form style

urlencode({"q": "Smith & Jones", "tag": "café"})
# 'q=Smith+%26+Jones&tag=caf%C3%A9'

unquote("caf%C3%A9")              # 'café'

quote treats / as safe because its usual job is encoding a path. When encoding a value that might contain a slash, pass safe="".

Java

// Form encoding — space becomes '+', NOT '%20'
URLEncoder.encode("Smith & Jones", StandardCharsets.UTF_8);
// → "Smith+%26+Jones"

Despite the name, URLEncoder implements application/x-www-form-urlencoded. It is correct for query values and wrong for path segments. For a full URI, use URI or a builder from your HTTP client.

Command Line

# Encode
python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "Smith & Jones"

# Decode
python3 -c 'import sys,urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' "caf%C3%A9"

# curl encodes a query parameter for you
curl -G https://example.com/search --data-urlencode "q=Smith & Jones"

Double Encoding

Because % is itself a special character, it encodes to %25. Encoding an already-encoded string therefore produces a broken URL:

Original:        Smith & Jones
Encoded once:    Smith%20%26%20Jones
Encoded twice:   Smith%2520%2526%2520Jones
Decoded once:    Smith%20%26%20Jones      ← the visible symptom

The tell-tale sign is %25 in a URL, or literal %20 sequences appearing in rendered output. It happens when a value is encoded in one layer and encoded again by another — a client that escapes before handing a value to a library that escapes too.

The fix is structural rather than a matter of decoding twice: encode exactly once, at the boundary where the URL is constructed. Never store encoded values; store raw values and encode at the point of use.

Troubleshooting

SymptomCauseFix
%20 appears literally on the pageValue decoded once too few times, or double-encodedTrace where encoding is applied; encode once
%25 in the URLDouble encodingRemove the redundant encoding layer
Query parameter truncated at &Unencoded & inside a valueEncode the value with encodeURIComponent / quote(safe="")
Accented characters become éBytes decoded as Latin-1 instead of UTF-8Decode as UTF-8; é is the classic mis-decode of é
Space becomes + where it should stay a spaceForm encoding applied to a path segmentUse %20 in paths
Everything after # missing on the serverFragments are never sent in the requestMove the value into the query string
%2F in a path rejected or normalisedMany servers refuse encoded slashes in paths by defaultRestructure the path, or enable it explicitly (AllowEncodedSlashes in Apache)
+ in an email address becomes a spaceForm decoding applied to user+tag@example.comEncode as %2B

URL Encoding Is Not Punycode

A related but separate mechanism handles non-ASCII domain names. café.com is converted to xn--caf-dma.com using Punycode (RFC 3492) as part of Internationalized Domain Names — not percent-encoding.

The split is worth remembering: the host is Punycode, everything after it is percent-encoded. Percent-encoding a hostname does not work.

Frequently Asked Questions

Is URL encoding a security measure? No. It is a data-representation mechanism. It does not sanitise input or prevent injection — encoding a value correctly stops it corrupting the URL's structure, which is a correctness property, not a defence against a malicious payload.

Should I encode the whole URL or just parts? Just the parts. Encode each path segment and each query value individually, then assemble. Encoding an assembled URL escapes its own separators.

Are %2F and / interchangeable? Not in a path. / is a segment separator; %2F is a literal slash inside one segment. Many servers reject or silently normalise %2F in paths, so avoid relying on it.

Does URL encoding affect SEO? Mildly. Consistency matters more than the choice itself: pick one canonical form and make sure internal links, canonical tags and sitemaps all agree, so encoded and unencoded variants do not become duplicate URLs.

Why do some sites show readable non-ASCII characters in the address bar? Browsers decode percent-encoded UTF-8 for display while sending the encoded form over the wire. The URL is still encoded in the request.

Encode and decode URLs, switch between encodeURI and encodeURIComponent modes, and inspect malformed sequences with the URL Encoder / Decoder. To break a URL into protocol, host, path and query parameters, use the URL Inspector & Parser — and for the Base64URL variant used inside JWTs, see the Base64 & Base64URL Encoder / Decoder.

Tags:#url encoding#percent encoding#encodeURIComponent#developer tools#web development

ProfitTool Team

Security & Dev

Written and maintained by the ProfitTool team. Where an article references a ProfitTool utility, the behaviour described is the behaviour of the tool on this site — you can open it and check the output yourself.

Try the Interactive Tools Mentioned

Free to use with no ProfitTool signup. Data handling varies by tool.