Encode a URL, or take one apart
Percent-encode text for a URL, decode it back, or run it through Base64. Both percent-encoding rules appear together, because choosing wrongly between them is the bug this page exists to catch.
How it works
A URL has a grammar. A slash separates path segments, a question mark opens the query, an ampersand divides one parameter from the next, a hash begins the fragment. Percent-encoding is how a value carries one of those characters without being read as punctuation: the byte is written as a percent sign followed by two hexadecimal digits, so a slash inside a value becomes %2F and the parser walks straight past it. A URL travels as ASCII, so every byte above 127 gets the same treatment.
encodeURIComponent and encodeURI escape different characters
Two functions do this job in JavaScript and they disagree about eleven characters. encodeURIComponent escapes the URL's own punctuation, so a slash, a question mark, an ampersand, an equals sign and a hash all come back as percent triples. encodeURI leaves every one of those as typed, on the assumption that the string handed to it is a finished URL whose punctuation is already doing its job. Both escape spaces, quotation marks, angle brackets and everything above ASCII. The gap between the two is narrow and it accounts for most of the trouble on this subject.
The failure is quiet, which is what makes it expensive. Suppose a search value contains an ampersand. Run that value through encodeURI and the ampersand survives into the query string, where the server reads it as the start of a new parameter: one field arrives truncated and a second, invented one turns up beside it. Nothing throws. The request is well formed. It carries a different meaning from the one intended, and the report of it lands weeks later as a bug about text going missing.
A plus sign is a space in a form and a plus in a path
HTML forms do not use plain percent-encoding. The application/x-www-form-urlencoded rules put a plus sign where a space was rather than %20, and that is why so much query-string handling turns a plus back into a space on the way in. A path segment has no such rule, and there a plus is a plus. Decode a Base64 value that arrived in a path using form rules and every plus in it quietly becomes a space, corrupting the payload without producing an error anywhere. The toggle on this page decides which reading you get.
The five characters JavaScript refuses to escape
RFC 3986 names a small unreserved set that needs no escaping anywhere: letters, digits and four punctuation marks, being the hyphen, the full stop, the underscore and the tilde. encodeURIComponent predates that document and keeps five characters beyond it, the exclamation mark, the apostrophe, the asterisk and the two round brackets. Most servers accept those without comment. OAuth 1.0 signatures and AWS request signing compute over the strict encoding instead, so a value holding an apostrophe produces a signature mismatch that reads like a credentials problem. The strict toggle escapes those five.
Base64 encodes bytes, and text has to become bytes first
btoa is the browser's Base64 encoder and it accepts a string of bytes rather than a string of text. Every character handed to it has to fall below U+0100, so btoa on an accented letter raises InvalidCharacterError, and btoa on any emoji does the same. The repair is to settle the encoding first. This page runs the text through TextEncoder, which emits UTF-8, and encodes those bytes. An accented e costs two bytes, a typical CJK character three, most emoji four. Decoding reverses both steps, so what returns is the text and not mojibake.
Base64URL is the same encoding with two substitutions and one omission. Plus becomes hyphen and slash becomes underscore, since both originals would need escaping inside a URL, and the trailing equals signs are dropped because a decoder recovers the length from the character count. A JSON Web Token is three Base64URL strings joined by full stops, so pasting a whole one into an ordinary Base64 decoder fails at the first full stop. The decoder here takes either alphabet, restores the padding itself, and splits a token into its parts.
Half-typed input is normal and should not throw
decodeURIComponent raises a URIError on a lone percent sign, and a lone percent sign is what every escape looks like one keystroke before it is finished. A page that throws there flashes an error at somebody doing nothing wrong. So the decoder here reads the input itself: an unfinished escape at the end is reported as unfinished while everything before it still decodes, and a character that can never be a hex digit is named along with its position. A Base64 string one character short of a whole group is treated the same way.
Questions
Which one do I want, encodeURIComponent or encodeURI?
encodeURIComponent, nearly always. Reach for it whenever you are building one piece of a URL: a query value, a path segment, a fragment. encodeURI suits a complete URL you already hold and only want to make legal, such as a link somebody pasted with a space in it. Using encodeURI on a value is the mistake this page is built around, because the value's own slashes and ampersands survive and change how the URL parses.
Why does my decoded text show question marks or strange letters?
The bytes were read as the wrong character set somewhere earlier. The sequence %C3%A9 is UTF-8 for an accented e; interpreted as Latin-1 it becomes two separate characters. If the input already holds those two characters, the damage happened before it reached this page and re-encoding cannot undo it. Find out what the producing system encodes as before suspecting the decoder.
Is Base64 a form of encryption?
No. It is a way of carrying arbitrary bytes through a channel that tolerates only text, and anybody can reverse it without a key. It turns up in data URLs, in email attachments, in HTTP basic authentication and in the first two parts of a JSON Web Token. In that last case the signature provides the security, while the payload sitting beside it is readable by anyone who pastes it into this page.
Should I encode a whole URL with this?
Only in the second mode, and only if the URL is otherwise correct. Percent-encoding an assembled URL cannot repair a value that was inserted without encoding, because by then the punctuation contributed by the value is indistinguishable from the URL's own. Encode each piece as you build it instead. The round-trip row will tell you whether what you have decodes back to exactly what you typed.
What does the round-trip row actually check?
It applies the inverse operation to the output and compares the result against your input, character for character. An encode should always match, and a mismatch means a lone surrogate or something else that could not survive the UTF-8 step. A decode often differs harmlessly, because hex case, characters left unescaped and a plus standing in for a space all decode to the same text, and the row names whichever of those it found.