Skip to content

URL Encode / Decode

Percent-encoding replaces characters that a URL cannot carry literally with a % followed by two hex digits — a space becomes %20, & becomes %26. This tool encodes and decodes in both directions and, critically, lets you choose between escaping a single component and preserving a whole URL.

Component mode uses encodeURIComponent(). Use this for a single query-string value, a path segment, or a form field — reserved characters like & = ? # / get escaped so they cannot break the surrounding URL.

Conversion runs as you type, entirely in your browser.

Component vs full URL: the distinction that matters

This is the one decision that determines whether percent-encoding helps you or breaks your link, and it is why this tool has a mode toggle rather than a single button.

Component mode (encodeURIComponent) escapes everything except A–Z a–z 0–9 - _ . ! ~ * ' ( ). That includes the reserved delimiters : / ? # [ ] @ ! $ & ' ( ) * + , ; =. Use it for one *value* — a query parameter, a path segment, a form field, a cookie value. The whole point is that if the value contains an & or a /, those characters get escaped so they cannot be mistaken for structure.

Full-URL mode (encodeURI) leaves the reserved delimiters alone and escapes only characters that are unsafe anywhere — spaces, quotes, angle brackets, non-ASCII. Use it when you have a complete address that is already structurally correct and you just want to make it legal. Running encodeURIComponent on a whole URL turns https://example.com/ into https%3A%2F%2Fexample.com%2F, which is correct only if you are embedding that URL as a parameter inside another URL.

InputComponent modeFull-URL mode
hello worldhello%20worldhello%20world
a&b=ca%26b%3Dca&b=c
https://x.com/p?q=1https%3A%2F%2Fx.com%2Fp%3Fq%3D1https://x.com/p?q=1
cafécaf%C3%A9caf%C3%A9
50% off50%25%20off50%25%20off

The a&b=c row is the whole story. In component mode the ampersand is escaped, so the value survives intact inside a query string. In full-URL mode it is left as a delimiter, so a receiving parser would read it as two separate parameters. Pick the mode by asking: is this thing a *value* inside a URL, or is it the URL?

Read the full explanation of url encode / decode results ↓

The plus-sign problem

A space is %20 in a path and + in an application/x-www-form-urlencoded query string. Both conventions are in active use, and they disagree. encodeURIComponent always produces %20, which is safe in both positions because a literal + in a query value must itself be escaped as %2B.

Where this bites is decoding. decodeURIComponent("a+b") returns a+b, not a b — it does not know it is looking at form encoding. If you are parsing a query string by hand, replace + with a space *before* percent-decoding. URLSearchParams in the browser does this for you correctly, which is one more reason not to parse query strings with split('&').

A password containing @, :, /, ? or # will break a connection string like postgres://user:pass@host/db unless it is component-encoded. This produces a parse error or a connection to the wrong host, not an authentication failure — which is why it is such a confusing outage to debug.

Non-ASCII, IDNs, and punycode

Percent-encoding operates on bytes, and modern URLs use UTF-8. é is two UTF-8 bytes, so it encodes to %C3%A9; an emoji is four bytes and produces four escapes. If you see %E9 (a single byte) for é, something upstream used Latin-1, and decoding it as UTF-8 will fail.

The hostname is the exception. Percent-encoding is not allowed there — internationalised domain names use punycode instead, so bücher.de becomes xn--bcher-kva.de on the wire. This is a DNS-level encoding, not a URL one, and it is why a non-ASCII domain resolves under a name that looks nothing like what you typed. If a lookup for an accented domain fails, convert it to its xn-- form first and try the DNS lookup with that.

Punycode is also a phishing vector: several Cyrillic letters render identically to Latin ones, so a homograph domain can be visually indistinguishable from a real one while being an entirely different name in DNS. Browsers now display the punycode form when a label mixes scripts, and a WHOIS lookup on the xn-- name will show you who actually registered it.

Double encoding

Encoding an already-encoded string escapes the % itself: %20 becomes %2520. The result decodes back to %20 rather than to a space, so the value arrives one layer short. This is the single most common percent-encoding bug, and it usually comes from a framework that already encoded a parameter before your code encoded it again.

The symptom is a literal %20 or %2F showing up in rendered output, a filename, or a log line. The tell is %25 in the encoded form — that is an escaped %, which only appears legitimately when your data genuinely contained a percent sign. This tool flags it: if a decode still leaves % sequences in the output, it says so, and decoding a second time gives you the original.

Double encoding is also a security concern. A filter that rejects ../ will happily pass %252e%252e%252f, which becomes %2e%2e%2f after one decode and ../ after two. If any layer of your stack decodes more than once, path traversal defences that look at the raw string are worthless. Decode exactly once, then validate.

Command-line equivalents

# Encode a component with curl, then print the URL it built
curl -s -o /dev/null -w '%{url_effective}\n' --get --data-urlencode 'q=a b&c' 'http://x/'
# -> http://x/?q=a+b%26c
# Note the +: --data-urlencode uses form encoding, so a space becomes '+',
# while the & is correctly escaped to %26. Both decode to "a b&c".

# Python one-liners, the most portable option
python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" 'a b&c'
python3 -c "import urllib.parse,sys; print(urllib.parse.unquote(sys.argv[1]))" 'a%20b%26c'

# jq, if it is already installed
jq -rn --arg v 'a b&c' '$v|@uri'

# Punycode conversion for an internationalised domain
idn2 'bücher.de'          # -> xn--bcher-kva.de
python3 -c "print('bücher.de'.encode('idna').decode())"

# Then look the real name up
dig +short xn--bcher-kva.de A
Percent-encoding without a browser

Note the safe='' argument in the Python example. By default urllib.parse.quote leaves / unescaped — it behaves like full-URL mode. Passing an empty safe set makes it behave like encodeURIComponent. Getting that argument wrong is the Python equivalent of choosing the wrong mode on this page.

Practical rules

  • Encode each value separately, then assemble the URL. Never encode the assembled URL and hope for the best.
  • Build query strings with URLSearchParams (JS), urlencode (Python), or your framework's equivalent rather than string concatenation. They handle both the escaping and the +/%20 question.
  • Never put credentials, tokens, or personal data in a query string at all. URLs land in server access logs, browser history, and Referer headers sent to third parties. Use a header or a request body.
  • Encode exactly once, at the boundary where the value enters the URL. Most double-encoding bugs are two layers each doing their job.
  • When debugging a redirect chain, decode each Location value before reading it — nested redirect_uri parameters are frequently double-encoded. The redirect checker will follow the chain for you.
Base64 Encode / Decode
Text ↔ Base64, UTF-8 safe

Frequently asked questions

What is the difference between encodeURI and encodeURIComponent?
encodeURIComponent escapes the reserved delimiters : / ? # & = as well as unsafe characters, so it is right for a single value going inside a URL. encodeURI leaves those delimiters intact so a complete URL stays functional. Use component mode for parameters and full-URL mode for whole addresses.
Why do I see %2520 in my URLs?
Double encoding. %20 was encoded a second time, turning its % into %25. Something in your stack escaped a value that had already been escaped. Decode twice to recover the original, then find and remove the redundant encoding step.
Is a space %20 or +?
Both, depending on position. %20 is correct everywhere; + means a space only inside an application/x-www-form-urlencoded query string. decodeURIComponent will not convert + back to a space, so replace + with a space first if you are hand-parsing a form-encoded query.
Do I need to encode non-ASCII characters?
In paths and query strings, yes — they become their UTF-8 bytes as percent escapes, so é is %C3%A9. In the hostname, no: domains use punycode (xn--…) instead, which is a DNS encoding rather than a URL one.
Why does my database connection string fail when the password has a @ in it?
Because @ separates the credentials from the host in a URI. An unescaped @ in the password makes the parser read the wrong host. Percent-encode the password in component mode — p@ss becomes p%40ss — or supply it through a separate configuration field instead.
What characters are never encoded?
The unreserved set: A–Z, a–z, 0–9, -, _, ., and ~. JavaScript's encodeURIComponent additionally leaves !, *, ', ( and ) alone, which is legal but occasionally surprises strict server-side validators.
Can I encode a URL to hide it?
No. Percent-encoding is fully reversible and provides no confidentiality at all — every browser and proxy decodes it automatically. It is a syntax mechanism, not a security one. Obfuscated URLs in emails are a phishing signal, not a protection.
Why did my search query break at the # character?
Everything after # is the fragment and is never sent to the server. An unescaped # in a query value truncates the request silently. Component-encode it to %23 and the whole value reaches the server intact.