URL Encoding Explained: Percent-Encoding, %20 vs + and Query Strings
URL encoding explained: how percent-encoding works, which characters are reserved, encodeURIComponent vs encodeURI, %20 vs + for spaces, and safe UTM links.
URL encoding, also called percent-encoding, replaces characters that aren't allowed in a URL, or that have a special meaning there, with a % sign followed by two hexadecimal digits. A space becomes %20, an ampersand becomes %26 and a pound sign becomes %C2%A3. It keeps values like search terms and campaign names from breaking the link.
If you've ever pasted a link and found it full of %20s, or seen a campaign report split one campaign into three because of stray spaces and symbols, you've met URL encoding. This guide explains which characters need encoding and why, how JavaScript's encoding functions differ, the long-running confusion between + and %20, and how to build tracking links that survive being shared.
To encode or decode anything on this page, use our free URL encoder and decoder. It offers encodeURIComponent, encodeURI and form modes, flags malformed sequences, and splits any URL into its parts.
Key takeaways
- URL encoding replaces unsafe or reserved characters with a % sign and two hex digits per UTF-8 byte, so a space becomes %20 and £ becomes %C2%A3.
- Use encodeURIComponent (or URLSearchParams) for individual values and encodeURI only for a complete, already-structured URL.
- A + means a space only in form-style query strings; in a path it's a literal plus, so %20 is the safe choice everywhere.
- Build UTM links with an encoder or URL builder, not by hand, to avoid broken parameters and double encoding such as %2520.
How percent-encoding works
A URL can only contain a limited set of ASCII characters. Everything else, including spaces, accented letters, currency symbols and emoji, has to be converted. The rules come from RFC 3986, the IETF standard for URI syntax:
- Convert the character to bytes using UTF-8.
- Write each byte as
%followed by two hex digits. - Use uppercase hex digits, which RFC 3986 recommends for consistency.
A plain ASCII character is one byte, so it becomes one triplet. Other characters take two to four bytes:
| Character | UTF-8 bytes | Encoded |
|---|---|---|
| Space | 1 | %20 |
& |
1 | %26 |
£ |
2 | %C2%A3 |
é |
2 | %C3%A9 |
€ |
3 | %E2%82%AC |
| 😀 | 4 | %F0%9F%98%80 |
Decoding reverses the process: read the bytes, then interpret them as UTF-8. If a system decodes with the wrong character set, you get garbled text such as £ instead of £.
Reserved and unreserved characters
RFC 3986 splits characters into groups:
- Unreserved characters never need encoding:
A–Z,a–z,0–9,-,.,_and~. The RFC says producers shouldn't percent-encode them. - Reserved characters have a job in URL syntax. The "general delimiters" are
: / ? # [ ] @, and the "sub-delimiters" are! $ & ' ( ) * + , ; =. - Everything else, including spaces,
%,",<,>and all non-ASCII characters, must always be encoded.
Reserved characters only need encoding when you want them as data rather than structure. In https://example.com/search?q=fish&page=2, the ?, = and & are doing their jobs. But if someone searches for "fish & chips", the & inside the value must become %26, or the server reads it as the start of a new parameter:
Broken: /search?q=fish & chips&page=2 → q = "fish ", plus a stray " chips" parameter
Correct: /search?q=fish%20%26%20chips&page=2 → q = "fish & chips"
The % sign itself must be encoded as %25, otherwise a decoder will try to read the next two characters as a byte.
Common characters and their encodings
| Character | Name | Encoded | Why it matters |
|---|---|---|---|
|
Space | %20 (or + in form data) |
Not allowed in URLs at all |
# |
Hash | %23 |
Starts the fragment, so anything after it isn't sent to the server |
% |
Percent | %25 |
Starts an encoded byte |
& |
Ampersand | %26 |
Separates query parameters |
+ |
Plus | %2B |
Read as a space in form-style query strings |
/ |
Slash | %2F |
Separates path segments |
= |
Equals | %3D |
Separates parameter names and values |
? |
Question mark | %3F |
Starts the query string |
@ |
At sign | %40 |
Can be read as user information before the host |
: |
Colon | %3A |
Separates the scheme and port |
The # row causes some of the most confusing bugs. A product called "Size #2" in an unencoded URL gets truncated at the hash, because browsers never send the fragment to the server.
encodeURIComponent vs encodeURI in JavaScript
JavaScript has two built-in functions, and choosing the wrong one is a classic bug. According to MDN, encodeURIComponent() leaves only A–Z a–z 0–9 - _ . ! ~ * ' ( ) unescaped. encodeURI() uses the same algorithm but also leaves URL syntax characters alone, such as ; , / ? : @ & = + $ #.
Here's the same string through each method:
const s = "Fish & chips £5/each? 50% off";
encodeURIComponent(s);
// "Fish%20%26%20chips%20%C2%A35%2Feach%3F%2050%25%20off"
encodeURI(s);
// "Fish%20&%20chips%20%C2%A35/each?%2050%25%20off"
new URLSearchParams({ q: s }).toString();
// "q=Fish+%26+chips+%C2%A35%2Feach%3F+50%25+off"
encodeURI left the &, / and ? untouched, so if you dropped its output into a query string, the value would break the URL. The rule:
| Function | Use it for | Don't use it for |
|---|---|---|
encodeURIComponent() |
A single value: a query parameter, a path segment, a redirect target passed as a parameter | A whole URL, because it encodes the :// and slashes |
encodeURI() |
A complete URL that is already correctly structured but may contain spaces or non-ASCII characters | Individual values, because it leaves &, = and ? alone |
URLSearchParams |
Building or reading whole query strings | Paths |
new URL() |
Parsing and modifying URLs safely | |
escape() |
Nothing. It's deprecated and doesn't handle UTF-8 properly |
In practice, URL and URLSearchParams are the safest options for most work, because they handle encoding for you:
const url = new URL("https://example.com/search");
url.searchParams.set("q", "fish & chips");
url.searchParams.set("utm_campaign", "autumn sale");
url.toString();
// "https://example.com/search?q=fish+%26+chips&utm_campaign=autumn+sale"
Two smaller details from MDN are worth knowing. encodeURIComponent() doesn't encode ! ' ( ) *, which RFC 3986 reserves, so a few strict APIs need an extra replacement step. And it throws a URIError if a string contains a lone surrogate, half of an emoji pair, which can happen when text is cut at a fixed length.
Other languages
| Language | Form style (space as +) |
RFC 3986 style (space as %20) |
|---|---|---|
| PHP | urlencode() |
rawurlencode() |
| Python | urllib.parse.quote_plus() |
urllib.parse.quote() |
| JavaScript | URLSearchParams |
encodeURIComponent() |
%20 vs +: which is right for a space?
Both, depending on where the space is.
%20is valid everywhere in a URL: paths, query strings and fragments.+means a space only inapplication/x-www-form-urlencodeddata, the format HTML forms use when they submit with GET or POST. The WHATWG URL Standard defines that format and serialises spaces as+.URLSearchParamsfollows it.- In a path,
+is a literal plus sign./products/C++refers to C plus plus, not "C" followed by two spaces.
This causes real bugs. decodeURIComponent("autumn+sale") returns autumn+sale, with the plus intact, because it doesn't use form rules. new URLSearchParams("c=autumn+sale").get("c") returns autumn sale. If a genuine plus sign in a value, such as a phone number like +44, isn't encoded as %2B, a form-aware server will turn it into a space.
The safe habit: encode spaces as %20 when you build URLs by hand, encode real plus signs as %2B, and let URLSearchParams or your framework handle form data.
UTM parameters and campaign links
Tracking links are where most businesses meet encoding problems. A UTM-tagged link adds parameters such as utm_source, utm_medium and utm_campaign to the query string, and analytics tools read them to attribute visits. Our UTM parameters guide covers naming conventions in depth. The encoding issues are:
- Spaces in values.
utm_campaign=autumn salemay arrive asautumn sale,autumn+saleorautumn%20saledepending on where it was pasted. Analytics tools usually decode them to the same value, but some email platforms and link shorteners mangle them. Avoid the problem entirely by using lowercase words joined with hyphens:autumn-sale. - Ampersands and hashes in values. A campaign named "Fish & Chips Friday" or "#1 Offer" breaks the link unless the
&and#are encoded. Better still, don't use them in campaign names. - Links that already have a query string. If the landing page URL already contains
?, append UTM parameters with&, not a second?. - Fragments. Parameters must come before any
#. In/page#offer?utm_source=x, the UTM part is inside the fragment and never reaches the server. - Double encoding. If an already encoded link is encoded again,
%20becomes%2520, because the%itself is encoded as%25. You'll see campaign names likeautumn%20salein reports. It usually happens when a link passes through two tools that each encode it.
Our free UTM builder assembles campaign links with the correct encoding, and the URL encoder decodes a suspicious link so you can see exactly what's in it. Agencies juggling several clients will find more link and campaign tools on our page of free tools for digital marketing agencies.
Encoded characters in page URLs and SEO
Non-ASCII characters in paths are allowed. Browsers show /café in the address bar but send /caf%C3%A9. Google can crawl both. Readability is the real concern. When a link with encoded characters is copied into an email or document, people see /caf%C3%A9-menu, which looks broken.
For English-language sites, the simplest approach is to keep slugs to lowercase ASCII letters, numbers and hyphens. Our slug generator converts titles into clean slugs, and the guide to SEO-friendly URLs covers structure and redirects. If you change URLs, check the old ones redirect properly.
Domain names are a separate system. International domain names such as café.example are converted to Punycode (xn--caf-dma.example), not percent-encoded.
Encoding is not security
Percent-encoding makes data safe to transport inside a URL. It doesn't make data safe to display or execute. Anyone can decode it, and attackers routinely encode payloads to slip past simple filters. Three rules:
- Encode for the context. URL-encode values going into URLs, HTML-escape values going into a page, and use parameterised queries for databases. One doesn't replace another.
- Decode once. Decoding repeatedly until nothing changes can turn
%253Cinto<, reopening the exact hole a filter was meant to close. - Validate redirect targets. A
?next=or?return=parameter that accepts any encoded URL can become an open redirect for phishing. Allow only your own paths or a fixed list of domains.
Like Base64 encoding, URL encoding is about representing data safely in a text format, not hiding it.
A quick checklist for building URLs
- Build URLs with
URLandURLSearchParams, or your framework's URL helper, rather than string concatenation. - Encode each value separately with
encodeURIComponentif you must build strings by hand. - Never run
encodeURIorencodeURIComponenton a URL that's already encoded. - Use
%20for spaces in paths and%2Bfor genuine plus signs. - Keep campaign names lowercase with hyphens, and put query strings before any
#. - Test the final link by clicking it, and decode it to check every parameter arrived intact.
If your team builds dozens of campaign links a week by hand, that's a good candidate for automation. Make It Flow can set up a simple workflow that generates consistent, correctly encoded links from a spreadsheet. Get in touch if that would help.
FAQ
What is URL encoding used for?
URL encoding converts characters that aren't allowed in URLs, or that have a special meaning there, into a safe format. It's used for search terms, form submissions, campaign parameters, file names and any text containing spaces, symbols or non-English characters, so the browser and server read the URL exactly as intended.
Why does a space become %20?
A space isn't allowed in a URL, so it's percent-encoded. The space character has the byte value 32, which is 20 in hexadecimal, so it becomes %20. In form-style query strings, a space may also be written as +, but %20 is valid everywhere in a URL.
What is the difference between encodeURI and encodeURIComponent?
encodeURIComponent() encodes everything except letters, digits and - _ . ! ~ * ' ( ), so it's right for individual values like query parameters. encodeURI() leaves URL structure characters such as / ? & = # alone, so it's only for complete URLs. Using encodeURI on a value lets & and = break the query string.
What does %2520 mean in a URL?
It means the URL was encoded twice. The first pass turned a space into %20. The second pass encoded the % sign as %25, producing %2520. Find the step that encodes an already encoded value, usually a link passing through two tools, and remove one of them.
Do I need to encode UTM parameters?
Yes, if the values contain spaces, ampersands, hashes, plus signs or non-ASCII characters. The easiest approach is to avoid them altogether by using lowercase words joined with hyphens, such as utm_campaign=autumn-sale. A UTM builder handles encoding automatically and stops parameters breaking when links are shared.
Is URL encoding the same as encryption?
No. URL encoding is a public, reversible format that anyone can decode instantly. It exists to transport text safely inside a URL, not to hide it. Never put passwords, tokens or personal data in a URL thinking encoding protects them, because URLs end up in browser history, server logs and analytics.
Related reading
- Regex Cheat Sheet: JavaScript Regular Expressions With UK and US Examples: JavaScript regex cheat sheet: character classes, quantifiers, groups, lookarounds, flags, UK postcode, US ZIP and phone patterns, and avoiding backtracking.
More guides for your industry
Free checklist (PDF)
Free marketing agencies website checklist
31 practical checks for marketing agencies: website speed and mobile, Google Business Profile and reviews, email security, booking and enquiry handling, and AI search visibility. Each one says why it matters, and many link to a free tool that checks it.
Written by the MakeItFlow team
We build the website audit these guides are based on. Every fact is checked against the primary sources listed above and this guide was last reviewed on 25 October 2026 . How we research and check
Check your own site in 30 seconds
Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.