Base64 Encoder and Decoder
Encode text to Base64 and decode Base64 back to text, in both directions, with the result updating as you
type. Text goes through UTF-8 bytes first — which is the whole point, because the browser's own btoa cannot
represent a Chinese character at all and quietly gets accented Latin wrong. Decoding accepts what real systems emit:
missing = padding, line breaks, and the URL-safe alphabet. When the bytes you decode are valid Base64 but
not text at all, this page tells you that instead of printing replacement characters.
Text → Base64
————Base64 → text
—————Valid Base64 — but these bytes are not UTF-8 text
———Nothing you paste is sent to us — both directions run in this page, in the tab you already have open. See our privacy policy.
How to use it
- Pick the direction first. There are two boxes, not one clever one. No tool can reliably tell
whether a string like
Zm9vis text you want encoded or Base64 you want decoded — it happens to be both, and those two readings give different answers. So you choose, and nothing is guessed. - Paste and read the result as you type. The output updates with every keystroke, so you can see immediately whether the encoding looks the way you expected. The Round trip line decodes the result again and compares it character by character with what you typed, which is a check rather than a feeling.
- Read the yellow notes before the result. They are where this tool says what it did on your behalf: whitespace removed, padding added, the URL-safe alphabet detected, and the one real warning — unpaired surrogate code units, which the encoder silently replaces and which therefore do not survive a round trip.
- Use the two "instead" buttons to change direction. Copying the result into the other box by hand is the step people get wrong: one stray newline and the Base64 is no longer the Base64.
Why the browser's own btoa is not enough
Every Base64 tool you have used calls the same two browser functions, btoa and atob, or an
equivalent. They work on bytes written as characters: each character in the input must be in the range 0–255,
one character per byte. That is the Latin-1 idea of text, and it stopped being a safe assumption on the web a long time
ago. Two different failures follow, and the second is the dangerous one.
It throws, loudly
A Chinese character like 中 is U+4E2D. There is no single byte for it, so btoa('中') raises
an InvalidCharacterError and produces nothing. Annoying, but at least it is honest.
It succeeds, quietly, with the wrong bytes
An accented Latin letter sits below 0xFF, so it is accepted — and encoded as one byte. café becomes
Y2Fm6Q==, which decodes to café only if the other side also assumes Latin-1. Anything that
follows the modern web — a JSON API, a database, a browser fetch — assumes UTF-8, where é is
two bytes, and the correct encoding of café is Y2Fmw6k=. The two strings differ, both look
perfectly plausible, and nothing about either one says which convention produced it. That is the defect this page
exists to avoid: not a crash, but a confident wrong answer.
The fix is one step, and this page does it in both directions: turn the text into UTF-8 bytes with
TextEncoder, encode those bytes as Base64, and on the way back turn the bytes into text with
TextDecoder. The table below shows what that does to the length, which is the part that surprises people.
| Text | Code points | UTF-8 bytes | Base64 |
|---|---|---|---|
f | 1 | 1 | Zg== |
Hello | 5 | 5 | SGVsbG8= |
café | 4 | 5 | Y2Fmw6k= |
中文 | 2 | 6 | 5Lit5paH |
😀 | 1 | 4 | 8J+YgA== |
Hello 世界 😀 café | 15 | 23 | SGVsbG8g5LiW55WMIPCfmIAgY2Fmw6k= |
Every row is computed on this page from the value you can see in the table, so you can check any of them by pasting the text into the box above and comparing. The last row is the one worth internalising: 15 characters become 23 bytes, because two Chinese characters cost three bytes each and the emoji costs four. Base64 encodes bytes, so the output is longer than the text — and if you are budgeting for a URL or a header value, the number you need is the byte count, not the character count. The tool prints both.
Standard and URL-safe: the same bytes, two alphabets
Base64 needs 64 characters and the base alphabet supplies 62 free: A–Z, a–z and
0–9. The last two have to come from somewhere, and there are two conventional choices. The standard
alphabet (RFC 4648, section 4) takes + and /. The URL-safe variant takes
- and _ instead, because + means space in a form-encoded body and both
+ and / have to be escaped inside a URL path or query.
Three consequences that matter in practice:
- The first 62 characters are identical, so a value that happens to contain neither
+nor/encodes to exactly the same string in both.5Lit5paHis not "standard" or "URL-safe" — it is both. - Mixing them is not a third alphabet. A string containing
+and-at once is invalid in both, and a tool that silently picks one produces different bytes from a tool that picks the other. This page rejects it and says why. - JSON Web Tokens use the URL-safe form. That is the most common place a developer meets
-and_in Base64 without realising it, and also where the missing padding comes from — see the next section.
If you copy a value from a URL or a token and paste it here, the page tells you which alphabet it found. If it contains neither special character, the tool says that too, rather than claiming it detected something it could not have.
Padding: the = signs, and why they are so often missing
Base64 reads the input three bytes at a time and writes four characters for each group. When the input length is not
a multiple of three there is a remainder of one or two bytes, and the encoding is finished off with two or one
= characters — not as data, but as a statement that the group was short.
That statement is redundant: from the length of the characters before the padding, the remainder can be computed.
So some systems keep the = signs (config files, PEM blocks, most command-line tools) and others drop them
(JWT, many query strings, some APIs). Both are read correctly here. When the padding is absent the tool adds it back
and says how many signs it added, so you are never left wondering whether your input was one character short or
deliberately unpadded.
The reverse case is worth knowing because it looks innocent: SGVsbG8== is not a shorter way of writing
SGVsbG8=. Seven data characters need exactly one padding character, so two of them is a contradiction —
which usually means the value was edited by hand or two values were joined. The tool reports it as a mismatch with both
numbers, rather than decoding the part it likes and ignoring the rest.
Why is my Base64 different?
This is the question that brings most people to a page like this one: the string is valid in one place and rejected in another, or two tools give two different strings for what is supposedly the same value. Almost always it is one of the causes below, and every one of them is visible if you look at the bytes instead of the characters. The hex view on this page is there for exactly that.
| What you see | What actually happened | How to check it here |
|---|---|---|
The value ends in Cg or Cg== and yours does not |
One of them has a trailing newline. Editors add one at the end of a file, so "Hello" and the file containing
Hello are different inputs. SGVsbG8= is Hello; SGVsbG8K is
Hello plus a line feed. |
Paste both and compare the byte counts and the hex view — the extra byte is 0a. |
| A long value with line breaks every 76 characters, and your tool rejects it | That is the MIME convention for email bodies and PEM files. The breaks are transport formatting and are not part of the data. | Paste it as-is. The breaks are removed before decoding, and the count is reported in the notes. |
Yours ends in = and theirs does not (or the reverse) |
One system kept the padding and the other dropped it. Neither is wrong; the padding is derivable from the length. | Decode either form. The Padding row tells you which case you had. |
Yours contains + or /, theirs contains - or _ |
One is standard Base64 and the other is URL-safe. Same bytes, different two characters. | Both are accepted; the Alphabet detected row names the one it found. |
| Same text, different length entirely | The two sides encoded different encodings of the text. 中 is e4b8ad in
UTF-8 but 2d4e in UTF-16LE, and legacy single-byte encodings give a third answer again. Windows
tooling and .NET APIs default to UTF-16LE more often than people expect. |
Decode both and compare the hex views. Letters separated by 00 bytes mean UTF-16. |
Your value starts with H4sI |
Not text at all — it is gzip-compressed data, whose first bytes are 1f 8b. Something compressed
the payload before encoding it, and the Base64 is a faithful copy of the compressed stream. |
The page reports that the bytes are not UTF-8 text and shows the hex, where 1f 8b is
visible. |
Decoding gives %3D, %2B or %2F where you expect =,
+, / |
The value went through URL encoding after being Base64-encoded, so the three characters that matter in a URL were percent-escaped. Decoding the Base64 first cannot work, because the percent signs are part of what you pasted. | Percent-decode the value before pasting it here. The error message names the position of the
%. |
| Yours looks right but the other side says it is invalid | Something stripped a character in transit. The usual suspects are a chat client that ate a +, a
spreadsheet that reformatted the cell, and a copy that stopped one character early — which leaves a length that
no Base64 can have. |
The error names the position of the bad character, or reports the impossible length with the count. |
Two of these are worth stating flatly, because they account for a large share of confused afternoons. The first is the trailing newline: a value pasted from a file usually has one and a value typed by hand does not, and one byte at the end changes the last four Base64 characters. The second is encoding: the same characters encode differently in UTF-8, UTF-16 and every legacy code page, which is why "the same string" can produce two Base64 values that both decode successfully to the same text on their own terms.
When valid Base64 is not text
Base64 is a byte encoding, not a text encoding. It will carry an image, a PDF, a compressed stream, an encrypted value or a TLS certificate just as happily as it carries a sentence, and it has no way to tell you which it is carrying. So there is a case that a decoder has to answer deliberately: the input is correct Base64, it decodes to bytes, and those bytes are not valid UTF-8.
There are only two things a tool can do, and one of them is worse than saying nothing:
- It can substitute replacement characters. Every byte sequence can be "decoded" this way — invalid
bytes become
U+FFFD, the black diamond question mark. The result is a string, it looks like output, and it is worthless: the user copies it, pastes it somewhere else, and the corruption is now in two places. - It can say the bytes are not text and show what they actually are. That is what this page does: the byte count, the offset of the first byte that breaks the rule, the class of breakage, and a hex view.
The offset and the class are the useful part, because they point at the cause. A break at the very end of the data in
the middle of a multi-byte sequence means the value was truncated — the classic "the API cut the response and the last
character of a Chinese string is missing". A break at the start, with a byte above 0xf4, means it was
never text. Bytes that alternate letter and 00 mean UTF-16, which is legal UTF-8 with NUL characters
rather than invalid UTF-8, so this page decodes it successfully and the hex view is where the pattern shows up. And a
handful of well-known first bytes tell you the format outright: 1f 8b is gzip, 89 50 4e 47 is
a PNG, 25 50 44 46 is a PDF.
Note what this page does not do: it does not guess an encoding for you. Detecting UTF-16, Latin-1 or a legacy code page from the bytes alone is unreliable, and a decoder that guesses wrong produces exactly the wrong text with no warning — the failure mode this whole page is organised against.
What this tool refuses, and why
Every entry below is something a tolerant decoder would accept, because bytes exist that make it "work". None of them says what was meant, so the tool refuses and names the problem instead of returning a value that is quietly different from the one you wanted.
| Input | Why it is refused | What to do instead |
|---|---|---|
A, or any value whose data length leaves one character over | Four Base64 characters hold three bytes, so a single leftover character holds nothing at all. This length cannot come from any byte sequence. | Check whether the copy stopped early — a truncated paste is the usual cause |
SGVs=bG8= | An = in the middle. Padding is only meaningful at the end; one in the middle means the value was joined with something else or edited, and there is no way to tell which part was meant. | Take the two values apart and decode them separately |
++-- | It mixes the standard alphabet with the URL-safe one, so it is not valid in either. Choosing one of them silently would give bytes you cannot check. | Decide which alphabet the source used and convert the two characters |
SGVsbG8== | Seven data characters call for one =, not two. The padding and the length disagree, so one of them was changed. | Use SGVsbG8=, or drop the padding entirely |
== | Padding with no data. There are no characters to decode. | Check what was actually copied |
Zm9v😀, and anything with full-width characters | Not in the alphabet. Emoji and full-width characters look like corruption and are usually the result of pasting into the wrong field. | The error reports the character and its position |
| More than 2,000,000 characters | A single-threaded conversion at that size locks the tab for long enough that the browser offers to kill the page — which looks like the tool having crashed. | Split the input and convert the parts |
The size limit is a deliberate choice rather than a technical ceiling: the conversion is plain arithmetic and a browser can do it for tens of megabytes if it is allowed to block, but a frozen tab with no output is not a slow result, it is a broken one. Two million characters is roughly a 1.5 MB file after decoding — far beyond the headers, tokens and config values this tool is for, and small enough that the result appears immediately. Above 300,000 characters the page paints a status line before starting, so even the slowest case shows something.
What leaves this page
Nothing. The page is a static file that imports one module of pure functions; the conversion, the UTF-8 handling, the validation and the hex view all run in the tab you already have open. There is no request to our server, no analytics event carrying your value, no cookie and no storage — reload the page and the text is gone, because it was never anywhere else.
That said, this is the worst category of tool to be careless with. Base64 is what an Authorization
header is made of, what most session cookies carry, and what an API key looks like when it is written down. A value
pasted into a tool that uploads is a credential handed over; a value pasted here stays local, but the clipboard, the
browser's own history of that text field and the screen you are sharing on a call are outside this page. Decoding a
token to read its claims is ordinary and safe; pasting a live key into anything is not, whatever the tool promises.
Common questions
- Is Base64 encryption? Can I use it to hide a password?
- No. Base64 is a way of writing bytes using 64 printable characters, and anyone can reverse it in one step — including this page, with no key and nothing to guess. It exists because some channels only carry text: JSON strings, email bodies, URLs, HTTP headers. If you need secrecy, use encryption; if you need to check the value was not corrupted, use a hash. A password that looks like random letters is not protected by being unreadable at a glance, and a token pasted into a public gist is still a live token.
- Why is the Base64 of my Chinese text so much longer than the text itself?
- Because Base64 encodes bytes, not characters, and the bytes come from UTF-8. One ASCII character is one byte, but a Chinese character is three bytes in UTF-8 and an emoji is four. Every three bytes become four Base64 characters, so a Chinese character costs four characters plus a third, while an ASCII letter costs about one and a third. The tool prints both numbers above the result — code points and UTF-8 bytes — so you can see the ratio instead of guessing at it.
- What is the difference between standard Base64 and URL-safe Base64?
- Only the last two characters of the alphabet. Standard Base64 uses + and /, which have special meanings in URLs and in some query strings; URL-safe Base64 uses - and _ instead, so the value can be pasted into a URL without escaping. The first 62 characters are identical, so anything that happens to contain no + or / encodes to exactly the same string in both. JSON Web Tokens are the best-known user of the URL-safe form.
- Why is the = padding missing from my Base64?
- Because the padding carries no information. Base64 works in groups of four characters holding three bytes, and when the input is not a multiple of three the leftover is signalled with one or two = signs. Those signs can be recomputed from the length of the data, so systems that care about compactness — JWTs, query strings, some config formats — drop them. This tool accepts both forms and tells you when it had to add padding back, so you can see what your input actually looked like.
- The tool says my Base64 is valid but not UTF-8. What does that mean?
- It means your input is correct Base64 — it decodes cleanly to bytes — but those bytes are not text in the UTF-8 encoding that virtually all of the modern web uses. Common causes: the value is an image, a PDF, a gzip stream or an encrypted blob; or it is text in UTF-16 and every other byte is a zero; or the data was cut short, which leaves the multi-byte sequence of a character incomplete at the end. The tool shows the byte count, the offset of the first byte that breaks the rule, and a hex view, because printing replacement characters would hand you a corrupted string that looks like a result.
- Is anything I paste here uploaded, logged or stored?
- No. The page imports one module of pure functions and runs it in the tab you already have open: there is no request to our server, no analytics event carrying your text, no cookie and no storage of any kind. That matters more for this tool than for most, because Base64 is what authorization headers, session cookies and API keys are made of — a pasted token is often a live credential. Even so, the clipboard, the browser history and the screen you are sharing are outside this page, so treat a live secret as live wherever you paste it.
No data of any kind. This page ships no tables, no sample payloads and no list of character encodings — every byte it shows is computed from what you typed, using the browser's own UTF-8 encoder and decoder and the arithmetic of RFC 4648.