URL Encoding Explained: Why %20 Appears and When to Encode
Why spaces turn into %20, which characters need encoding, and how to build query strings and UTM links that do not break.
You paste a link into a chat and it turns into https://example.com/my%20page. Or an analytics report shows a campaign called diwali%20sale%20%26%20more. The percent signs are not corruption. They are URL encoding, and knowing when it is needed (and when it is harmful) prevents a surprising number of broken links and mislabelled campaigns.
What percent encoding is
A URL can safely carry only a limited set of characters. Some characters have a job inside the URL: ? starts the query string, & separates parameters, = separates a name from its value, # starts the fragment and / separates path segments. Other characters, such as spaces, accented letters, the rupee sign and emoji, are not allowed at all.
Percent encoding handles both cases. The character is converted to UTF-8 bytes, and each byte is written as % followed by two hexadecimal digits. A space is one byte (hex 20), so it becomes %20. The letter é is two bytes, so it becomes %C3%A9. The rupee sign is three bytes, so it becomes %E2%82%B9.
The URL encoder does this using the browser's encodeURIComponent function, and it can also decode in the other direction.
Characters that are safe and characters that are not
Letters A to Z and a to z, digits 0 to 9, and the symbols - _ . ~ are never changed. The function also leaves ! * ' ( ) alone. Everything else is encoded, including the characters that carry meaning in a URL.
| Input | Encoded | Why |
|---|---|---|
hello world | hello%20world | space is one byte |
café | caf%C3%A9 | é is two UTF-8 bytes |
₹500 | %E2%82%B9500 | rupee sign is three bytes |
50% off | 50%25%20off | the percent sign itself must be encoded |
a+b | a%2Bb | a literal plus must not be read as a space |
a/b | a%2Fb | slash would otherwise split a path |
#top | %23top | hash would otherwise start a fragment |
| an emoji such as a smiley | %F0%9F%98%80 | four UTF-8 bytes |
The pattern to remember: the number of %XX groups equals the number of UTF-8 bytes. Hindi, Japanese and other scripts produce three bytes per character in most cases, so a short word becomes a long string of percent groups. That is normal.
Encode the value, not the whole URL
This is the most common mistake. encodeURIComponent is designed for a single value that will sit inside a URL, so it encodes slashes, colons, question marks and ampersands. Take the address https://example.com/a?b=1&c=2 and encode all of it, and you get https%3A%2F%2Fexample.com%2Fa%3Fb%3D1%26c%3D2. That is correct only if the whole address is itself a value inside another URL. As a link on its own, it no longer works.
A decision rule you can apply every time:
- Is it a whole address you want to click? Do not encode the structure. Encode only the parts that contain spaces or non-English text, such as a path segment or a value.
- Is it a value that will go after an
=? Encode it fully. - Is it a URL that goes inside another URL? Encode it fully, as a value.
Here is case three in practice. A redirect link needs to carry the address https://shop.example.com/cart?item=12&qty=2. Without encoding you would write https://example.com/go?url=https://shop.example.com/cart?item=12&qty=2, and the receiving server sees qty=2 as a separate parameter of the go link, so the inner address is cut off at item=12. With encoding, you get https://example.com/go?url=https%3A%2F%2Fshop.example.com%2Fcart%3Fitem%3D12%26qty%3D2, and the whole inner address arrives as one value.
Spaces: %20 or plus?
Two conventions exist. In the path, a space must be %20. In HTML form submissions and many query strings, a plus sign also stands for a space, and most servers accept either in the query part. The catch is decoding: decoding a+b with decodeURIComponent gives back a+b, with the plus sign untouched. If a value came from a form and you decode it with the tool, plus signs may need to be read as spaces by hand. When you are writing a value yourself, use %20 for spaces and %2B for a real plus, and there is nothing to guess.
Worked example: a UTM link that breaks
The UTM builder takes one line with four comma-separated values: the URL, the source, the medium and the campaign. It adds utm_source, utm_medium and utm_campaign to the URL. It does not encode what you type.
Enter https://example.com/offer, newsletter, email, diwali-sale and you get a clean result: https://example.com/offer?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-sale.
Now enter a campaign name with natural wording, diwali sale & more. The output is https://example.com/offer?utm_source=newsletter&utm_medium=email&utm_campaign=diwali sale & more. That link has a raw space and a raw ampersand. Browsers often fix the space on their own, but the ampersand ends the campaign value early, so analytics records the campaign as diwali sale and treats more as a separate, meaningless parameter.
There are two fixes. Encode the campaign name first, with the URL encoder: diwali sale & more becomes diwali%20sale%20%26%20more, and that is what you type into the builder's fourth field. Or, better, avoid special characters in campaign names altogether: diwali-sale needs no encoding and reads cleanly in reports. The builder also appends with & instead of ? when your URL already has a question mark, so a link like https://example.com/search?q=shoes is handled correctly.
Another example from the same builder: a campaign named 50% off comes out as utm_campaign=50% off. A bare percent sign followed by a space is not valid percent encoding. Encoded properly it is 50%25%20off, though 50-off is simpler.
Double encoding and decoding errors
Double encoding happens when already-encoded text is encoded again. The % in %20 becomes %25, giving %2520. Encoding a b twice produces a%2520b. If you see %25 followed by two hex digits in a link, something encoded it twice; decode once and compare. The rule that prevents this: encode raw values exactly once, at the moment you assemble the URL.
Malformed input is the opposite problem. Decoding a string such as 100%, where the percent sign is not followed by two hex digits, fails with a "URI malformed" error in the browser function the tool uses. The same happens with an incomplete multi-byte sequence like %E0%A4%A. If decoding fails, look for a stray percent sign or a link that was cut off when it was copied.
Clean path segments: encode less, not more
If you are building page addresses, the better approach is often to avoid encoding at all. A title like Diwali Sale 2026 can be encoded as Diwali%20Sale%202026, but a lowercase hyphenated slug such as diwali-sale-2026 is shorter, easier to read and easier to share. The slug generator creates that form. Encoding is for data that has to be carried as is; slugs are for names you choose.
Limits: what this does not cover
- Encoding is not security. Anyone can decode
%XXtext instantly, so never use it to hide passwords or tokens. If you need to carry binary data as text, that is a different technique; see the Base64 encoder, which also is not encryption. - The URL encoder works on one piece of text at a time. It does not parse a full URL, split it into parts or encode each parameter for you. You choose what to paste.
- It uses a single encoding function,
encodeURIComponent. It does not offer a mode that keeps slashes and colons intact for whole-address encoding, and it does not convert domain names with non-English characters into their internationalised form. - The UTM builder produces three parameters only, with no
utm_termorutm_content, and falls back togoogle,cpcandcampaignif you leave a field empty, which can label your traffic wrongly. Do not use commas in any field, since commas separate the fields. - Servers and frameworks differ in how they treat plus signs, repeated parameters and very long URLs. If a link works in your browser but not in an application, test it where it will actually be used.
The short version: encode a value once, as late as possible, and keep names you control free of spaces and symbols so you rarely need to encode at all.