URL Encoder & Decoder
Encode and decode percent-escapes with the right scheme. Shows what stays unescaped in each, handles UTF-8 correctly, and flags anything it could not read exactly.
encode
RFC 3986. Escapes everything except letters, digits, - . _ ~. The safe default.
Hello%2C%20world%21%20Caf%C3%A9%20%E6%97%A5%E6%9C%AC
Encoded: Hello%2C%20world%21%20Caf%C3%A9%20%E6%97%A5%E6%9C%AC
Runs in this browser tab. Nothing is uploaded, and the text is not stored or logged.
How to use this tool
A URL can only carry a restricted set of characters safely. Anything else — a space, a slash, a `?`, a non-ASCII letter — has to be written as `%` followed by two hex digits, which is what percent-encoding is. The awkward part is that there is more than one rule for deciding which characters need it, and picking the wrong one produces a URL that looks fine and fails.
- 1`encode` turns text into escapes, `decode` reads them back, and `query` reads a whole query string into a table.
- 2In `encode`, choose the scheme by where the value is going: a path segment, a query value, a whole URL, or a form body.
- 3In `decode`, tell it whether a `+` is a space. It usually is — and when it is not, nothing in the text will tell you.
There are four rules, not one
JavaScript ships two encoders and a form parser, and they do not agree. This page offers all four schemes explicitly, because choosing between them from memory is how URLs break.
| Scheme | Leaves unescaped | Use it for |
|---|---|---|
| strict | A–Z a–z 0–9 - . _ ~ | Anything, when you are unsure. The modern default. |
| component | …plus ! * ' ( ) | Matching encodeURIComponent exactly. A query value or a path segment. |
| full | …plus / ? : @ & = + $ # ; | A whole URL, so its structure stays readable. |
| form | A–Z a–z 0–9 * - . _ | application/x-www-form-urlencoded. A space becomes +. |
The one to watch is `component`, because it is what most people reach for. It leaves `!'()*` unescaped, and those are legal in a URI *component* but not in a URI *identifier* — so a server that reads RFC 3986 strictly will reject a request that looks completely fine in the browser bar.
The + that is sometimes a space
A space can be written two ways: `%20`, which works anywhere, or `+`, which only works in an `application/x-www-form-urlencoded` context. A query string is one, so `?q=hello+world` means `hello world`. A path segment is not, so `/a+b` in a path is a literal plus sign.
The problem is that nothing in the string says which context you are in. A decoder has to be told, and this page defaults to treating `+` as a space — because a query string is the more common paste — with a checkbox for the other case.
# a query string
curl 'https://api.example.com/search?q=hello+world' # "hello world"
# a path segment - the + is a plus
curl 'https://api.example.com/files/hello+world' # "hello+world"UTF-8, and the order that matters
A character like `é` is one character in JavaScript and two bytes in UTF-8, and each byte gets its own escape. The order matters: encode to UTF-8 first, then escape each byte. Escaping the character directly gives `%E9`, which is Latin-1, and every server expecting UTF-8 misreads it.
"é" -> %C3%A9 correct: UTF-8 bytes, one escape each
-> %E9 wrong: the code point escaped as if it were a byte
"🎉" -> %F0%9F%8E%89 four bytes, because it is a surrogate pair in UTF-16A four-byte character is where hand-rolled encoders break. A surrogate pair is two code units in JavaScript, so encoding unit by unit gives you six bytes where the character is four — or a replacement character if something trims the invalid half.
Don't encode twice
Encoding an already-encoded string turns `%20` into `%2520`, and there is no way to tell from the result that it happened. The value still looks like a URL, which is what makes it hard to spot. If something is already encoded, decode it first and see whether it changes.
a b -> a%20b or a+b in a form body
a%20b -> a%2520b now it decodes to "a%20b", not "a b"
If decoding a value twice gives you something different the first time did,
you have found the double encoding.Reading a query string
The `query` mode shows parameters as a table rather than handing you a joined string, which is usually what you want when you are debugging a request. Two things it deliberately does not do:
- It keeps repeated names as separate rows. `?tag=a&tag=b` is two parameters, and `URLSearchParams.toString()` will hand you `tag=a%2Cb` — one parameter with a comma in it, which is a different request.
- It keeps a name with an empty value. `?c=` is a present parameter with no value, and dropping it loses the fact that it was sent.
When this page says it could not read something
A `%` starts an escape, and the two characters after it have to be hex digits. `%ZZ` is not a character, and `%E9` on its own is not valid UTF-8 — it is a Latin-1 `é` that a UTF-8 reader cannot finish.
Rather than drop those or turn them into `�`, which is indistinguishable from a real character, this page keeps what it can and tells you what it could not read. You get the best available answer and the warning that it is not exact — because a mangled string pasted into a request is a much harder afternoon than a labelled one.
100% sure a stray %. Kept as "%", and flagged.
caf%E9 a Latin-1 é. Kept as a replacement, and flagged.
a=%2 a truncated escape. Kept, and flagged.Doing it yourself
For anything but a whole-URL rewrite, the built-ins are enough and a library is more dependency than the job needs. The one case worth reaching for a library is building a query string from structured data, where the escaping of the *names* is easy to forget.
encodeURIComponent("hello world") // "hello%20world"
decodeURIComponent("hello%20world") // "hello world"
// a query string, names included - the part people forget
const q = new URLSearchParams({ q: "a b", "a key": "v" }).toString()
// "q=a+b&a+key=v"
// a whole URL, structure intact
encodeURI("https://example.com/a b?c=d") // "https://example.com/a%20b?c=d"Frequently asked questions
- What is the difference between encodeURI and encodeURIComponent?
- encodeURI leaves a URL's structure alone — the slashes, question marks, ampersands and hash — so it is for a whole URL. encodeURIComponent escapes them, so it is for one piece of it: a path segment or a query value. Using encodeURIComponent on a whole URL turns every separator into %2F and you get one long path.
- When should I use strict rather than component?
- Strict, unless you have a reason. `component` matches encodeURIComponent, which leaves ! ' ( ) unescaped. That is legal in a URI component but not in an identifier, and a server reading RFC 3986 strictly will reject it. Strict has no such edge case.
- Is a + a space?
- In a query string, usually. In a path segment it is a literal plus sign. Nothing in the text distinguishes them, so the decoder here asks, defaulting to space because a query string is the more common paste.
- Why does my encoded value look double-encoded?
- It is. Encoding something twice turns %20 into %2520, and the result still looks like a URL. Decode it and see whether the second decode changes it — if it does, the first pass was redundant.
- What does the warning about not reading something exactly mean?
- The input contained a `%` that was not a valid escape, or bytes that are not valid UTF-8. The text shown is the best available reading, but it is not a faithful decode, so encoding it again will not get you back to what you started with. Decoding `%E9` as a replacement character is exactly the failure this avoids.
- Does this send my URL anywhere?
- No. Everything happens in your browser tab. Nothing is uploaded, stored or logged — which matters more here than for most tools, since a URL often carries a token or an identifier worth keeping to yourself.