Base64 Encoder and Decoder
Encode and decode Base64 with correct UTF-8 handling. Supports the URL-safe alphabet, missing padding and PEM-style wrapped input that breaks naive atob/btoa.
Standard alphabet: uses + and /, and pads the result with = characters.
Key Takeaways
- btoa('ş') throws InvalidCharacterError outright: btoa only understands Latin-1, so the naive path breaks on any non-ASCII text.
- The correct order is always: encode the text to UTF-8 bytes with TextEncoder, then Base64-encode those bytes.
- The standard alphabet uses
+and/; the URL-safe alphabet uses-and_, which is what JWTs and query strings require. - Base64 is neither encryption nor compression: it hides nothing and makes the data roughly 33 percent larger.
Why btoa fails on non-ASCII text
The browser btoa function does not take text. It takes a binary string, meaning every character must have a code point between 0 and 255, the Latin-1 range. The first character above 255 makes it throw rather than return anything. Turkish letters such as ş, ğ, İ and ı all sit outside that range, so an ordinary sentence is enough to break it, and the same applies to Greek, Cyrillic, CJK or any emoji.
btoa('Kürşad');// Uncaught InvalidCharacterError:// String contains an invalid character // 'ü' is inside Latin-1, so this passes but encodes the wrong bytesbtoa('ü'); // single byte 0xFC// In UTF-8 'ü' is two bytes: 0xC3 0xBCThe second case is the more dangerous one. Because the character falls inside Latin-1 no exception is raised, yet the Base64 carries a single-byte Latin-1 code rather than the UTF-8 bytes. The consumer, which expects UTF-8, sees mojibake, and the root cause is hard to trace precisely because nothing ever threw.
The correct path: bytes first, then Base64
Base64 encodes bytes, not characters. That makes the pipeline inherently two-step: convert the text to UTF-8 bytes explicitly, then convert those bytes to Base64, and reverse the same route when decoding. This tool works exactly that way, so non-ASCII input neither throws nor comes back garbled.
// Encode: text -> UTF-8 bytes -> Base64function encode(text) { const bytes = new TextEncoder().encode(text); let binary = ''; for (const b of bytes) binary += String.fromCharCode(b); return btoa(binary);} // Decode: Base64 -> bytes -> UTF-8 textfunction decode(b64) { const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0)); return new TextDecoder('utf-8').decode(bytes);} encode('Kürşad'); // 'S8O8csWfYWQ='decode('S8O8csWfYWQ='); // 'Kürşad'Note
Alphabets, padding and whitespace
Base64 has two alphabets in common use. The standard variant (RFC 4648 section 4) uses + and / for values 62 and 63. The URL-safe variant (section 5) substitutes - and _, and usually drops the trailing padding as well. The difference is not cosmetic: a + in a query string decodes back as a space, and a / is read as a path separator.
| Aspect | Standard | URL-safe |
|---|---|---|
| Character 62 | + | - |
| Character 63 | / | _ |
| Padding | = pads to a multiple of 4 | Usually stripped |
| Typical use | Email attachments, PEM, data URIs | JWT, URL paths, query strings, cookies |
| Safe in a URL | No, needs re-encoding | Yes |
The = character pads the encoded text to a multiple of four. Since every 3 bytes become 4 characters, a remainder of 1 byte yields two characters plus two pad characters, and a remainder of 2 bytes yields three characters plus one. That is why a length of 1 modulo 4 is impossible: no encoder ever emits a lone trailing character. When you see it, the text was truncated during a copy, and this tool reports that case specifically instead of surfacing an opaque native error.
Pasted input containing newlines is extremely common too. PEM-formatted certificates and keys wrap their Base64 body at 64 characters, and email attachments wrap similarly. That whitespace is formatting, not data, and it must be stripped before decoding. This tool removes all whitespace and line breaks automatically, so you can paste a PEM body straight in.
Base64 is not encryption
This is the most persistent misconception about the format and it is worth correcting directly. Base64 is an encoding, not a cipher. There is no key, no secret component, and the decoding algorithm is entirely public. Base64-ing a password or an API key before dropping it into a config file protects nothing; it only makes the value unreadable at a glance, which is a very different property from confidentiality.
Warning
So what is Base64 actually for? Moving binary data through channels that can only carry text. Email bodies, JSON string fields, XML elements, URL components and HTTP headers are the usual cases. The goal is safe transport of bytes, never concealment of their contents.
Decoding binary payloads
Not every Base64 string represents text. Decode a PNG, a protocol buffer or an encrypted blob and you get bytes that are not valid UTF-8. Forcing those bytes through a text decoder fills the screen with U+FFFD replacement characters and tells you nothing. Worse, if you copy that output and re-encode it, the original bytes are gone, because a replacement character is a lossy substitution with no way back.
This tool decodes in strict UTF-8 mode: when the bytes are not valid text it says so rather than substituting silently, and hands back a hexadecimal preview of the leading bytes instead. Knowing that PNG files start with 89 50 4E 47, JPEG files with FF D8 FF, and ZIP archives with 50 4B 03 04 is usually enough to identify what you are actually holding.
Frequently Asked Questions
- Is the data I paste sent to a server?
- No. Encoding and decoding run entirely in JavaScript in your browser, and no network request carries your input. Once the page has loaded you can disconnect and the tool keeps working.
- I encoded accented text and another tool gave a different result. Which one is right?
- The other tool is most likely interpreting the text as Latin-1 rather than UTF-8. In UTF-8 a letter such as ş is two bytes; in Latin-1 it does not exist at all. The output that treats the source as UTF-8 is the correct one, and this tool always encodes through TextEncoder as UTF-8.
- Should I use the standard or the URL-safe alphabet?
- Choose URL-safe when the value travels in a URL path, a query string, a cookie or a JWT segment. Standard is fine everywhere else. For decoding the distinction hardly matters here, since this tool accepts both alphabets.
- Can I remove the trailing equals signs?
- Most decoders accept unpadded input, and this tool restores the missing padding itself. Some strict implementations do not, so keep the padding if the value is going to another system. Contexts such as JWT, where unpadded is the convention, are the exception.
- Can I use Base64 to hide a password?
- No. Base64 is a public encoding anyone can reverse, it involves no key, and it carries no security value whatsoever. Use a password hashing algorithm such as bcrypt or Argon2 for passwords, and real encryption or a secrets manager for confidential values.