[{"data":1,"prerenderedAt":43},["ShallowReactive",2],{"$fC8eQUFl8T5j-7ifkilg8h5zf8ONONEnE0HQLldRYxfw":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"tools":13,"draft":17,"html":18,"toc":19,"minutes":42},"base64-and-url-encoding-explained","Base64 and URL Encoding Explained, and Why btoa() Fails on Arabic","What Base64, Base64URL and percent-encoding really do to your bytes, why btoa() breaks on Arabic and accents, and when a + means a space.","2026-10-04",[9,10,11,12],"encoding","utf-8","javascript","urls",[14,15,16],"base64-encode-decode","url-encode-decode","image-to-base64",false,"\u003Cp>Type \u003Ccode>btoa('مرحبا')\u003C\u002Fcode> into a browser console and you get an \u003Ccode>InvalidCharacterError\u003C\u002Fcode>. Try \u003Ccode>btoa('café')\u003C\u002Fcode> and it works, but a server decoding the result as UTF-8 gets garbage. Both encodings behind ToolOnTap’s \u003Ca href=\"\u002Fbase64-encode-decode\">Base64\u003C\u002Fa> and \u003Ca href=\"\u002Furl-encode-decode\">URL\u003C\u002Fa> tools look trivial, and both have a trap that only shows up once your text isn’t plain English. Arabic text walks straight into both traps, so I built the tools around them.\u003C\u002Fp>\n\u003Ch2 id=\"text-is-bytes-first\">Text is bytes first\u003C\u002Fh2>\n\u003Cp>Base64 and percent-encoding both work on \u003Cstrong>bytes\u003C\u002Fstrong>. Text has to become bytes before either can touch it, and the rule for that step decides whether the result round-trips.\u003C\u002Fp>\n\u003Cp>UTF-8 is the rule the web uses. Plain English letters take 1 byte each, accented Latin letters and Arabic letters take 2, and most emoji take 4. “مرحبا” is five letters and 10 bytes. “café ☕” is six characters and 9 bytes.\u003C\u002Fp>\n\u003Cp>\u003Ccode>btoa()\u003C\u002Fcode> predates all of that. It takes a string where every character must have a code below 256 and treats each character as one byte. Arabic letters are far above 255, so it throws. \u003Ccode>é\u003C\u002Fcode> is 233, so it passes, but it’s written as the single byte \u003Ccode>E9\u003C\u002Fcode>, the Latin-1 form, instead of the two UTF-8 bytes \u003Ccode>C3 A9\u003C\u002Fcode>. The output, \u003Ccode>Y2Fm6Q==\u003C\u002Fcode>, decodes to broken text everywhere that expects UTF-8. The correct UTF-8 Base64 for “café” is \u003Ccode>Y2Fmw6k=\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>So both tools always go through \u003Ccode>TextEncoder\u003C\u002Fcode> first, which produces UTF-8 bytes, and only then encode:\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> function\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> bytesToBase64\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">bytes\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> Uint8Array\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#b35309;--shiki-dark:#FFAB70\">urlSafe\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> false\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> bin \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> ''\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> CHUNK\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0x8000\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#57606a;--shiki-dark:#8b949e\">  \u002F\u002F String.fromCharCode(...huge) would overflow the call stack\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  for\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">let\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> bytes.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> CHUNK\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) bin \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> String.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">fromCharCode\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">...\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">bytes.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">subarray\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(i, i \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> CHUNK\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> b64\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> btoa\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(bin)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">  return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> urlSafe \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">?\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> b64.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">replace\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#1a7f37;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold\">\\+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">g\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'-'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">replace\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#1a7f37;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold\">\\\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">g\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'_'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">replace\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#DBEDFF\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">+$\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#cb2431;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> b64\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Each byte becomes one character with a code from 0 to 255, which is exactly what \u003Ccode>btoa()\u003C\u002Fcode> accepts. The chunking matters for files: spreading a few megabytes of bytes into one \u003Ccode>String.fromCharCode(...)\u003C\u002Fcode> call overflows the call stack, so it goes 32,768 bytes at a time. The tests run a 1,000,000-byte array through it and compare the result with Node’s own Base64.\u003C\u002Fp>\n\u003Ch2 id=\"how-base64-works\">How Base64 works\u003C\u002Fh2>\n\u003Cp>Base64 takes 3 bytes (24 bits), cuts them into four 6-bit groups, and writes each group as one of 64 characters: \u003Ccode>A\u003C\u002Fcode>–\u003Ccode>Z\u003C\u002Fcode>, \u003Ccode>a\u003C\u002Fcode>–\u003Ccode>z\u003C\u002Fcode>, \u003Ccode>0\u003C\u002Fcode>–\u003Ccode>9\u003C\u002Fcode>, \u003Ccode>+\u003C\u002Fcode> and \u003Ccode>\u002F\u003C\u002Fcode>. That’s why encoded data is a third larger than the original.\u003C\u002Fp>\n\u003Cp>When the input isn’t a multiple of 3 bytes, the last group is padded with \u003Ccode>=\u003C\u002Fcode>. One leftover byte gives two characters and \u003Ccode>==\u003C\u002Fcode>, two leftover bytes give three characters and \u003Ccode>=\u003C\u002Fcode>. “hello” is 5 bytes, so it ends with one \u003Ccode>=\u003C\u002Fcode>: \u003Ccode>aGVsbG8=\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>That rule also gives a cheap way to catch a damaged string. Without its padding, a valid Base64 string can never have a length that leaves 1 when divided by 4, because a lone last character holds only 6 bits, too few for a byte. The decoder checks exactly that and says “one character too long or too short” instead of guessing.\u003C\u002Fp>\n\u003Ch2 id=\"base64url-same-idea-url-safe-alphabet\">Base64URL: same idea, URL-safe alphabet\u003C\u002Fh2>\n\u003Cp>\u003Ccode>+\u003C\u002Fcode> and \u003Ccode>\u002F\u003C\u002Fcode> mean something in URLs, and \u003Ccode>=\u003C\u002Fcode> does too in query strings. Base64URL (RFC 4648, section 5) swaps \u003Ccode>+\u003C\u002Fcode> for \u003Ccode>-\u003C\u002Fcode> and \u003Ccode>\u002F\u003C\u002Fcode> for \u003Ccode>_\u003C\u002Fcode>, and usually drops the padding. The same three bytes \u003Ccode>FB FF BF\u003C\u002Fcode> are \u003Ccode>+\u002F+\u002F\u003C\u002Fcode> in standard Base64 and \u003Ccode>-_-_\u003C\u002Fcode> in Base64URL. JWTs use it, and so do many APIs that put binary IDs in links.\u003C\u002Fp>\n\u003Cp>The “URL-safe (Base64URL, no padding)” option does that swap when encoding. When decoding, you don’t choose: the decoder accepts both alphabets, with or without padding, and ignores line breaks, so Base64 copied from an email or a wrapped config value works as it is.\u003C\u002Fp>\n\u003Cp>What people paste is often messier than a clean string, so the decoder also:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>strips a \u003Ccode>data:…;base64,\u003C\u002Fcode> prefix, so a data URI from HTML or CSS decodes directly;\u003C\u002Fli>\n\u003Cli>points at the first bad character with its position, such as ““$” isn’t a Base64 character (position 4)”;\u003C\u002Fli>\n\u003Cli>tries to read the bytes as UTF-8 in strict mode, and if they aren’t text, looks at the first bytes to recognize a PNG, JPEG, GIF, PDF, ZIP or WebP file and offers it as a download.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"percent-encoding-has-a-different-job\">Percent-encoding has a different job\u003C\u002Fh2>\n\u003Cp>URL encoding solves another problem. A URL can contain only a limited set of characters, and some of those have a structural meaning: \u003Ccode>\u002F\u003C\u002Fcode> separates path parts, \u003Ccode>?\u003C\u002Fcode> starts the query, \u003Ccode>&amp;\u003C\u002Fcode> and \u003Ccode>=\u003C\u002Fcode> separate parameters, \u003Ccode>#\u003C\u002Fcode> starts the fragment. Any other byte is written as \u003Ccode>%\u003C\u002Fcode> followed by two hex digits. A space becomes \u003Ccode>%20\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Non-English text goes through UTF-8 here too. “مرحبا” becomes \u003Ccode>%D9%85%D8%B1%D8%AD%D8%A8%D8%A7\u003C\u002Fcode>, two escapes per letter because each letter is 2 bytes. \u003Ccode>é\u003C\u002Fcode> becomes \u003Ccode>%C3%A9\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>The real decision is \u003Cstrong>what you’re encoding\u003C\u002Fstrong>, and the tool asks it directly:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Option\u003C\u002Fth>\n\u003Cth>Built on\u003C\u002Fth>\n\u003Cth>Leaves alone\u003C\u002Fth>\n\u003Cth>Use it for\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>“A value”\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>encodeURIComponent\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>A–Z a–z 0–9 - _ . ! ~ * ' ( )\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>One query value or path segment\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>“A full URL”\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>encodeURI\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>also \u003Ccode>: \u002F ? # &amp; =\u003C\u002Fcode> and the rest of the URL’s structure\u003C\u002Ftd>\n\u003Ctd>A whole link with spaces or Arabic in it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Cp>Encoding a value as “A full URL” leaves its \u003Ccode>&amp;\u003C\u002Fcode> alone, and the server then sees two parameters where you meant one. Encoding a full URL as “A value” turns \u003Ccode>https:\u002F\u002F\u003C\u002Fcode> into \u003Ccode>https%3A%2F%2F\u003C\u002Fcode>. The test file has one line for each: \u003Ccode>a b&amp;c=d\u002Fé\u003C\u002Fcode> as a value is \u003Ccode>a%20b%26c%3Dd%2F%C3%A9\u003C\u002Fcode>, and a full URL keeps its \u003Ccode>?\u003C\u002Fcode>, \u003Ccode>=\u003C\u002Fcode> and \u003Ccode>&amp;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"when-means-a-space\">When + means a space\u003C\u002Fh2>\n\u003Cp>HTML forms send data as \u003Ccode>application\u002Fx-www-form-urlencoded\u003C\u002Fcode>, where a space is written as \u003Ccode>+\u003C\u002Fcode>. In a URL path, a \u003Ccode>+\u003C\u002Fcode> is just a plus sign. Both conventions are in daily use, so neither can be the default, and the tool makes it an option: “Spaces as +” when encoding and “Read + as a space” when decoding, both marked “(form data)”.\u003C\u002Fp>\n\u003Cp>The encoding side is a single replacement after \u003Ccode>encodeURIComponent\u003C\u002Fcode>. That’s safe because \u003Ccode>encodeURIComponent\u003C\u002Fcode> has already turned every real plus sign into \u003Ccode>%2B\u003C\u002Fcode>, so the only \u003Ccode>+\u003C\u002Fcode> signs in the output are the former spaces. \u003Ccode>a+b c\u003C\u002Fcode> becomes \u003Ccode>a%2Bb+c\u003C\u002Fcode>, and decoding it with the option ticked gives \u003Ccode>a+b c\u003C\u002Fcode> back.\u003C\u002Fp>\n\u003Cp>The decoder is also strict where browsers’ own function fails with a bare \u003Ccode>URIError\u003C\u002Fcode>. A literal \u003Ccode>%\u003C\u002Fcode> that isn’t followed by two hex digits, as in “100% sure”, is reported with its position and the fix (write it as \u003Ccode>%25\u003C\u002Fcode>). Escapes that don’t form valid UTF-8, such as a lone \u003Ccode>%C3\u003C\u002Fcode>, get their own message, because that usually means the text was cut in the middle of a character or encoded with another character set.\u003C\u002Fp>\n\u003Ch2 id=\"reading-long-links\">Reading long links\u003C\u002Fh2>\n\u003Cp>Decode a full URL and the tool also splits it into protocol, host, path, fragment and query parameters, with each value decoded. It uses the browser’s \u003Ccode>URL\u003C\u002Fcode> parser for that, so it splits the link the same way the browser does. Repeated parameters, such as \u003Ccode>tag=a&amp;tag=b\u003C\u002Fcode>, stay as separate rows instead of being merged. That’s the view I wanted for tracking links and redirects, where the real destination is usually a parameter several escapes deep.\u003C\u002Fp>\n\u003Ch2 id=\"try-it\">Try it\u003C\u002Fh2>\n\u003Cp>Paste text into the \u003Ca href=\"\u002Fbase64-encode-decode\">Base64 encoder and decoder\u003C\u002Fa> or the \u003Ca href=\"\u002Furl-encode-decode\">URL encoder and decoder\u003C\u002Fa>. Both convert as you type, and the “Decode this” button sends the result back the other way, so you can check that it round-trips. For images in HTML or CSS, \u003Ca href=\"\u002Fimage-to-base64\">Image to Base64\u003C\u002Fa> gives you a ready data URI. The rest are on the \u003Ca href=\"\u002Fdev-tools\">developer tools\u003C\u002Fa> page.\u003C\u002Fp>\n",[20,24,27,30,33,36,39],{"id":21,"text":22,"level":23},"text-is-bytes-first","Text is bytes first",2,{"id":25,"text":26,"level":23},"how-base64-works","How Base64 works",{"id":28,"text":29,"level":23},"base64url-same-idea-url-safe-alphabet","Base64URL: same idea, URL-safe alphabet",{"id":31,"text":32,"level":23},"percent-encoding-has-a-different-job","Percent-encoding has a different job",{"id":34,"text":35,"level":23},"when-means-a-space","When + means a space",{"id":37,"text":38,"level":23},"reading-long-links","Reading long links",{"id":40,"text":41,"level":23},"try-it","Try it",5,1792325801095]