URL encoder
Percent-encoding replaces characters that would otherwise mean something in a URL. Component encoding and form-body encoding differ by more than a space: a form body also escapes characters encodeURIComponent leaves alone. This page shows those two plus encodeURI for a complete URL, rather than making you guess which one you have.
A developer sees, side by side, what a string becomes under each URL percent-encoding, and is told which one to use for the destination they actually have.
Encode or decode
Result
Decoded something larger? Send it to the tool chain to keep going.
- In / out
- ·
- Escaped
- ·
- Arm
- ·
- Requests
- · from Resource Timing
- Script
- · KB of a 100 KB budget
The same string, three encodings
Take hello world/foo?x=1. Component encoding is
hello%20world%2Ffoo%3Fx%3D1, because a space, a slash, a question
mark, and an equals sign would otherwise be URL syntax. Form-body encoding is
hello+world%2Ffoo%3Fx%3D1: the same reserved characters, but a
space becomes a plus. encodeURI for a complete URL is
hello%20world/foo?x=1, because it leaves the delimiters in place.
Paste that string here and the three rows should match those three strings.
Which encoding, by destination
A query value and a path segment want component encoding, where a space is
%20. A form body wants
application/x-www-form-urlencoded, where a space is
+. A fragment is a component too. A complete URL that already
has its delimiters in place wants encodeURI, which leaves
: / ? # & = alone so the URL keeps working.
The plus sign, which is why you are here
There are two percent-encodings in daily use. They are not identical except for a
space. On ~_.-!*() a, component encoding is
~_.-!*()%20a and form encoding is %7E_.-%21*%28%29+a.
They differ for ~, !, (, ) and
the space.
Component encoding is what encodeURIComponent produces.
A space becomes %20. RFC 3986 section 2.3 names letters, digits,
-, ., _ and ~ as unreserved;
encodeURIComponent also leaves !, *,
(, ) and ' literal, which that unreserved set
does not. Use it for a single value going into a path segment, a query value or a
fragment.
Form encoding is the
application/x-www-form-urlencoded serializer, which is what an HTML form
posts and what most server frameworks assume when they parse a query string. A space
becomes +.
Encode one way, decode the other, and a space turns into a literal plus or a genuine
plus turns into a space. That second one is the expensive version: a plus in an email
address, a signature or a base64 string is meaningful, and turning it into a space
corrupts the value rather than merely looking odd. The decode result row is
decodeURIComponent, the widest of the three going backwards, and Copy
first result copies that row. Encode-mode rows name encodings. Decode is not an encoding, so its set name is -. Raising the pass ceiling changes how many times decode runs, not the label. If the input contains a
plus, both readings are printed beside it, labeled form data and RFC 3986, and neither
of those labels is marked as chosen.
The third one, and when not to use it
encodeURI is for a complete URL, and it deliberately leaves
: / ? # & = alone so the URL keeps working. That makes it exactly
wrong for encoding one value: a value containing an ampersand or an equals sign will
pass through untouched and then be read as a parameter boundary by the receiving
server.
It is shown here because it is genuinely the right answer when the whole thing you are holding is a URL with a space in it. The set name beside that row says it matches no WHATWG encode set, because naming the nearest set would be a claim the row does not keep.
Hosts are not percent-encoding
A non-ASCII host is IDNA-encoded, not percent-encoded. Mixing the two is how a host gets percent-encoded when it should not. When the input parses as a URL, the parts table shows the host in the IDNA form the WHATWG parser produces, and says so beside that cell. The three encode rows still percent-encode the whole string, including the host, because that is what those three functions do.
Decoding a string that was encoded twice
When the decoded result is itself a URL, this breaks it into scheme, userinfo, host, port, path, query and fragment. Recursive decode sets a ceiling of 1 to 16 passes and then reports how many passes actually changed the string. If the input already contains a percent-escape, decode mode says so before you have to notice leftover percent signs.
One thing the parts table quietly demonstrates: the fragment, the piece after the
#, is the only part of a URL a browser never sends to a server. That is
why every tool on this site puts shareable state there.
What this deliberately does not do
It does not wrap output into 76-character lines. That wrapping is an RFC 2045 MIME convention, and a wrapped percent-encoded URL is not a URL. It does not take a file; per-line encode and decode cover the bulk job. It does not convert a host to punycode as its own product, and it does not do base64 or hex. Those are a different row.
The three named rows are UTF-8. There is no charset control on this page. Bytes in another charset belong with the fourth row, which is out of this version.
Questions
Which encoding should I use?
If you are building a query string by hand or putting a value into a path, use the
component form with %20. If you are reproducing what a browser form
would send, use the form body version with +. When you are decoding
something and do not know its origin, look at both plus readings and pick the one
that reads like real data.
Why does my decoded string still have percent signs in it?
It was probably encoded twice, which happens whenever a URL containing a value
gets put inside another URL as a parameter. Raise the pass ceiling and decode
again. If a %25 appears in the input, that is an encoded percent
sign. It is consistent with double encoding, and it is also what a literal
percent sign becomes after a single encode pass.
What does "not valid percent-encoding" mean?
A % has to be followed by exactly two hexadecimal digits. A stray
percent sign in ordinary text, such as "50% off", is not valid encoded input, and a
decoder that guessed at it would be inventing data. A well-formed escape that is
not valid UTF-8 is the same class: the decoder rejects it rather than guessing.
Is my URL uploaded?
No. Encoding and decoding are both a few lines of arithmetic and both happen in this tab. That matters more than it sounds here: URLs routinely carry session tokens and API keys, and this is one of the tools people paste those into without thinking about it.
Related tools
For = padding rather than % escapes, you want
base64. If the decoded value turns out to be JSON, the
JSON formatter will indent it and locate any syntax
error, and the tool chain will do both steps on one paste.