Base64 Decoder
Decode Base64 to text, bytes, or a downloadable file — tolerant of the real-world mess.
Whitespace, URL-safe characters, missing padding and data: URIs become position-anchored errors.
Press ? for shortcuts
Do it offline:$ base64 -d encoded.txt
How to use it
- Paste the Base64 into the input pane, or drop a text file that contains it. Decoding runs as you type, and anything the decoder tolerated — ignored whitespace, URL-safe characters, missing padding, a stripped data: URI prefix — is declared as an info chip, never applied silently.
- Read the result where it lands: valid UTF-8 stays in the text pane, while binary bytes switch to a hex view with the detected file type, an inline preview when the bytes are a PNG or JPEG, and a download button named after what the bytes actually are.
- Turn on strict mode when you need to validate rather than recover: every tolerance becomes a position-anchored error, exactly as an RFC 4648-conformant parser would refuse the same input.
- Copy the decoded text, download it as a file, or use Share to put the whole tool state — input and strict setting — into a link that never touches a server. Clear and Load sample are undoable for the whole session.
Paste any Base64 — standard or URL-safe, wrapped or unpadded, even a whole data: URI — and the decoded result appears as you type. Text renders as text; binary bytes switch to an honest hex view with magic-byte type detection, an image preview and a file download. Every tolerance the decoder applies is surfaced as a chip rather than applied silently, and nothing you paste leaves this device.
How it works
RFC 4648 §4 assigns each of 64 characters — A–Z, a–z, 0–9, + and / — a six-bit value, so a group of four characters carries exactly 24 bits: three bytes of the original data. Decoding is that table read in reverse. Each character is mapped back to its six bits, the bits are concatenated, and every 24-bit run is cut back into bytes. Nothing about the transformation is secret or lossy, which is why decoding needs no key and always reproduces the original bytes exactly.
The final group is where padding earns its keep. When the original data is not a multiple of three bytes, the last group encodes one byte as two characters or two bytes as three, and = fills the remaining positions so the total length stays a multiple of four. On the decode side that arithmetic runs backwards: a final group of two characters yields one byte, three characters yield two, and the leftover low bits are discarded. Because the length remainder alone determines how much padding is absent, this decoder can restore missing = signs — and because no group can encode anything as a single character, a length of 4n+1 is proof of corruption rather than a repairable omission.
Before any bits are mapped, the input gets one recovery pass. Whitespace is skipped, a data: URI header is cut away, and the URL-safe characters of RFC 4648 §5 — - and _ — are translated to their §4 counterparts, with each repair reported as a chip and every error position still counted against the untouched original. Strict mode disables the pass entirely, taking the position RFC 4648 §3.3 prescribes for conforming decoders: non-alphabet characters are rejected, not ignored.
The decoded bytes then face one last question: are they text? The page runs them through a fatal-mode UTF-8 decoder and scans for null and control bytes that no legitimate text contains. Clean UTF-8 lands in the text pane; anything else is treated as a file — magic-byte sniffing names its real type, and the hex view shows the bytes themselves. That final check is why this tool never prints mojibake: output is either exactly your text or honestly binary.
Examples
SGVsbG8sIFdvcmxkIQ==Hello, World!The reference vector. The trailing == says the final group carries a single byte — and if the padding got lost somewhere in transit, this decoder accepts the same string without it.
Y2Fmw6kg4piVcafé ☕The encoder page's Unicode example run in reverse — the same twelve characters round-trip through UTF-8 in both directions. A tool that shows café instead has read the UTF-8 bytes as Latin-1.
U0dWc2JHOHNJRmR2Y214a0lRPT0=SGVsbG8sIFdvcmxkIQ==Base64 that decodes to more Base64 — the classic double-encoding accident, usually a pipeline that encoded an already-encoded value. The tool notices the output's shape and offers Decode again: one more pass yields Hello, World!
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==binary output — 70 bytes, sniffed image/png (magic bytes 89 50 4e 47 0d 0a 1a 0a)
hex view + image preview · Download .pngA complete 1 × 1 pixel PNG inside a data: URI. The prefix is stripped and recorded as a chip with the declared type; the type on the download button comes from the decoded bytes' magic numbers, never from what the URI claims — the two agree here, but only the bytes are trusted.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Length is checked first — and one missing character is fatal
Base64 maps every 3 bytes to 4 characters, so a valid string's length is always a multiple of 4 once padding is counted. Unpadded input is easy to repair — the decoder accepts it and shows “Missing padding was accepted” — because the missing amount is implied by the remainder. A length of 4n+1, though, cannot come out of any encoder: a character of real data has gone missing. That is why a truncated paste fails with “Base64 length is invalid — 1 stray character” instead of decoding to something subtly wrong.
URL-safe input is recognized, not configured
JWTs, URL parameters and filenames use the RFC 4648 §5 alphabet, which swaps + for - and / for _. There is no variant switch on this page: the decoder accepts both alphabets and tells you what it saw with the chip “URL-safe alphabet detected (- and _ accepted)”. In strict mode the same characters become position-anchored errors with a note pointing out that the input looks URL-safe — useful when some other consumer insists on the standard alphabet and you need to know your data will fail it.
A data: URI's label can lie; magic bytes cannot
Paste a whole data:image/png;base64,… URI and the prefix is stripped rather than refused, with a chip recording the declared MIME type. The declared type is only a claim, though: the download button and the image preview are driven by the decoded bytes' own signature — PNG, JPEG, PDF, ZIP or GZIP — so mislabelled data gets named for what it is, not for what it says it is.
Binary output never masquerades as text
If the decoded bytes are not clean UTF-8 — or they carry null bytes or control characters that no real text contains — a text pane would show mojibake: characters that were never in the data. This tool refuses to do that. The output switches to a hex view — sixteen bytes per row with an offset gutter and an ASCII column — plus a download button, and the switch itself is announced, so what you copy or save is always the real bytes rather than a rendering accident.
A byte-order mark is surfaced, never swallowed
Some encoders prepend the UTF-8 BOM — the three bytes EF BB BF — and text that carries one can break string comparisons, shell scripts and JSON parsers downstream. When decoded text starts with one, this page shows the chip “Decoded text begins with a UTF-8 byte-order mark” and leaves the bytes exactly as they were: stripping it silently would hide the very thing you may be debugging.
What strict mode actually changes
The default mode is a recovery tool: it ignores whitespace (“Whitespace and line breaks were ignored”), accepts both alphabets and unpadded input, and strips data: URI prefixes — each recovery declared as a chip. Strict mode is a conformance check: the same deviations become errors at the exact offending position, and the data: prefix is no longer stripped. Use it when you are validating what another system will accept, not when you just want your data back.
Wrapped MIME and PEM bodies paste straight in
Base64 in the wild is often hard-wrapped — MIME email bodies at 76 columns, PEM blocks at 64 — and copying it brings the line breaks along. Those breaks are not part of the data, so the decoder ignores them and says so. One thing it will not repair: two Base64 values concatenated together. Padding may only end a value, so the first stray = in the middle is reported at its position instead of guessing where to split.
Common errors
Invalid Base64: character '%' at position 17- A character outside both Base64 alphabets, reported at its 1-based position. A % usually means the string is URL-encoded on top of Base64 — run it through a URL decoder first. The input stays untouched so you can fix it in place.
Invalid Base64: character '=' at position 8- Padding may only appear at the very end of a value, so a = in the middle usually means two Base64 strings were joined — a concatenated log field, or a template that pasted one value after another. Split them at the reported position and decode each on its own.
Base64 length is invalid — 1 stray character- The cleaned input's length is one past a multiple of four, and no encoder can produce that: a character was lost or added in transit. Truncated pastes and cut-off log lines are the usual cause — recover the missing tail rather than trimming the stray.
Invalid Base64: character '_' at position 41 — this looks like URL-safe Base64; switch the variant to URL-safe.- Strict RFC 4648 accepts only + and /, so the first URL-safe character — here inside a JWT-style signature segment — is refused at its exact position. On this page the fix is simply turning strict mode off: the tolerant default accepts both alphabets and records the substitution as a chip.
Base64 length is invalid — padding is required in strict mode- The tolerant default infers the missing = from the length; strict mode will not, because RFC 4648 §4 requires padded output. Either add the padding — one = after a final group of three characters, two after a group of two — or leave strict mode off.
Input is larger than the 16 MB limit for this tool. For bigger payloads use the CLI: base64 -d encoded.txt- The page's soft limit is a refusal, never a truncation: your input stays exactly as pasted. The command line decodes any size — and if base64 -d is not recognized, older macOS spells the flag -D and GNU coreutils also accepts --decode.
FAQ
Why won't my Base64 string decode?
Three causes cover almost every failure. A character outside both alphabets — often a % from URL encoding, or a quote mark that rode along with the copy — is reported at its exact position. A length one past a multiple of four means the string was truncated. And a = in the middle means two values were joined. The error always names the position, the input is never altered, and Jump to error puts the cursor on the offending character.
Do I have to restore the missing = padding first?
No. The remainder of the length already determines how much padding is missing, so the decoder fills it in and tells you it did with a chip. The only mode that insists on padding is strict mode, because RFC 4648 §4 formally requires it — that is a conformance question, not a data-recovery one.
How is URL-safe Base64 different from standard Base64?
Two characters. The standard alphabet uses + and /, which break inside URLs and filenames; the URL-safe variant (RFC 4648 §5) substitutes - and _ and usually drops the padding. The encoded bytes are identical. This page accepts both alphabets without being told which to expect and reports the detection as a chip — a JWT segment pastes in as-is.
Is decoding Base64 the same as decrypting it?
No, and the difference matters. Base64 is a reversible transport encoding with no key — this page just reversed yours, and so can anyone else. If you can read a value by pasting it here, it was never protected. Treat Base64-wrapped credentials in configs and logs as plain text when you assess what has been exposed.
Can I decode Base64 into an image or another file?
Yes. When the decoded bytes are binary, the tool inspects their magic numbers — PNG, JPEG, PDF, ZIP and GZIP signatures — and offers a download named for the detected type, with anything unrecognized saved as .bin. PNG and JPEG bytes also get an inline preview. A pasted data: URI works whole; its declared type is shown, but the detection trusts the bytes.
Why did the output turn into a hex table?
Because rendering those bytes as text would be a lie. Output that is not valid UTF-8 — or that carries null or control bytes — switches to a hex view: sixteen bytes per row, an offset gutter, and an ASCII column with unprintable bytes shown as ·. It is a real table with row and column headers, and a plain-text export exists for screen readers and diffs. Copy still works — it copies the dump.
My decoded output is still Base64 — what happened?
Somewhere upstream an already-encoded value was encoded again — commonly a pipeline that Base64-encodes a field that already held Base64, or a header stuffed inside another header. The tool recognizes the output's shape and offers Decode again, which replaces the input with the output and can be undone. Very short values may not trigger the offer; decoding a second time by hand does the same thing.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Decode now — run a large input immediately, or force a re-run.
- ⌘/Ctrl + S
- Download the output — decoded text as .txt, binary as its detected file type (on this page it downloads instead of saving the page).
- ⌘/Ctrl + C
- Copy the decoded output when nothing is selected and focus is in the tool; in the hex view this copies the plain-text dump. A live text selection still copies normally.
- ⌘/Ctrl + Shift + C
- Copy the output even while a selection is active.
- ? or ⌘/Ctrl + /
- Open the shortcut help overlay, including the browser-conflict notes and the single-key kill switch.
- Esc
- Close the topmost dialog — it never clears the input.
- t
- Cycle the color theme (outside text fields; single-key shortcuts can be disabled).