Base64 strings show up everywhere — inside JWTs, HTTP Authorization headers, email attachments, API responses, and data URLs. Decoding them back to their original form is a routine task for any developer, but there are a handful of gotchas (padding, URL-safe variants, encoding assumptions) that trip people up. This guide covers every scenario with working code examples.
Quick answer
If you just need a fast decode without writing code, paste your Base64 string into Toolmingo's Base64 Encoder/Decoder. It handles standard Base64 and Base64url and runs entirely in your browser.
For code, here's the one-liner in each language:
| Language | Decode to string |
|---|---|
| JavaScript (browser) | atob(base64str) |
| JavaScript (Node.js) | Buffer.from(base64str, 'base64').toString('utf8') |
| Python | base64.b64decode(s).decode('utf-8') |
| Go | base64.StdEncoding.DecodeString(s) |
| PHP | base64_decode($str) |
| CLI (macOS/Linux) | echo "SGVsbG8=" | base64 -d |
Read on for the full context, error handling, and edge cases.
How Base64 decoding works
Base64 encodes every 3 bytes of binary data into 4 ASCII characters (6 bits each). Decoding reverses this: every group of 4 characters maps back to 3 bytes.
Base64: S G V s b G 8 =
Index: 18 6 21 44 1 6 60 (pad)
Bits: 010010 000110 010101 101100 000001 000110 111100
Bytes: 01001000 01100101 01101100 01101111
Text: H e l o
The = padding character fills the last group when the original input wasn't a multiple of 3 bytes. A single = means 1 byte of padding; == means 2 bytes.
Decoding Base64 in JavaScript
Browser: atob()
const encoded = "SGVsbG8sIFdvcmxkIQ==";
const decoded = atob(encoded);
console.log(decoded); // Hello, World!
atob() (ASCII to Binary) is built into every browser. It decodes to a binary string — a string where each character represents one byte. For plain text that's fine; for binary files you need extra steps (see below).
Gotcha: atob() throws DOMException: Failed to execute 'atob' if the input contains characters outside the Base64 alphabet, including whitespace. Strip whitespace first:
function safeAtob(str) {
const clean = str.replace(/\s+/g, '');
return atob(clean);
}
Node.js: Buffer
// Decode to text
const encoded = "SGVsbG8sIFdvcmxkIQ==";
const decoded = Buffer.from(encoded, 'base64').toString('utf8');
console.log(decoded); // Hello, World!
// Decode to raw bytes (e.g. an image or binary file)
const bytes = Buffer.from(encoded, 'base64');
// bytes is a Buffer — write to file, pass to an API, etc.
Buffer.from(str, 'base64') is more forgiving than atob(): it silently ignores invalid characters (including spaces and newlines), which makes it easier to handle multi-line Base64 from email headers or PEM files.
Decode a Base64 data URL
A data URL looks like data:image/png;base64,iVBORw0KGgo.... Strip the prefix before decoding:
function decodeDataUrl(dataUrl) {
const [, base64] = dataUrl.split(',');
return Buffer.from(base64, 'base64'); // returns a Buffer
}
Decoding Base64 in Python
import base64
encoded = "SGVsbG8sIFdvcmxkIQ=="
# Decode to bytes, then to string
decoded = base64.b64decode(encoded).decode('utf-8')
print(decoded) # Hello, World!
# Decode to raw bytes (for images, files, etc.)
raw_bytes = base64.b64decode(encoded)
Handling padding errors: If the Base64 string has incorrect padding, Python raises binascii.Error: Incorrect padding. Fix by adding missing = characters:
import base64
def safe_b64decode(s: str) -> bytes:
# Base64 length must be a multiple of 4; add padding if needed
padded = s + '=' * (4 - len(s) % 4) if len(s) % 4 else s
return base64.b64decode(padded)
Validate missing: Python's base64.b64decode() silently ignores some invalid characters. Use validate=True to catch them:
base64.b64decode(encoded, validate=True) # raises binascii.Error on bad chars
Decoding Base64 in Go
package main
import (
"encoding/base64"
"fmt"
)
func main() {
encoded := "SGVsbG8sIFdvcmxkIQ=="
// Decode to bytes
data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
panic(err)
}
fmt.Println(string(data)) // Hello, World!
}
Go has two decoders; choose the right one based on context:
| Decoder | Characters | Use for |
|---|---|---|
base64.StdEncoding |
+ and / with padding |
Standard Base64 |
base64.URLEncoding |
- and _ with padding |
URL-safe Base64 |
base64.RawStdEncoding |
+ and / without padding |
Standard, no = |
base64.RawURLEncoding |
- and _ without padding |
JWTs, URL tokens |
Using the wrong decoder is the most common Go Base64 mistake — StdEncoding will return an error if it encounters - or _ characters.
Decoding Base64 in PHP
<?php
$encoded = "SGVsbG8sIFdvcmxkIQ==";
// Decode to string
$decoded = base64_decode($encoded);
echo $decoded; // Hello, World!
// With strict mode (returns false on invalid input instead of silently ignoring chars)
$decoded = base64_decode($encoded, strict: true);
if ($decoded === false) {
throw new RuntimeException('Invalid Base64 input');
}
PHP's base64_decode() returns false on failure — always check the return value when strict: true is set:
$result = base64_decode($input, strict: true);
if ($result === false) {
// Handle error: invalid characters or malformed input
}
Base64url decoding
Base64url is a URL-safe variant that replaces + with - and / with _, and usually omits padding =. It's used in JWTs, OAuth tokens, and many modern APIs.
JavaScript:
function decodeBase64url(str) {
// Replace URL-safe chars with standard Base64 chars and add padding
const standardBase64 = str
.replace(/-/g, '+')
.replace(/_/g, '/')
.padEnd(str.length + (4 - str.length % 4) % 4, '=');
return atob(standardBase64);
}
// Decode a JWT payload (the second segment)
function decodeJwtPayload(token) {
const payload = token.split('.')[1];
return JSON.parse(decodeBase64url(payload));
}
Python:
import base64
def decode_base64url(s: str) -> bytes:
return base64.urlsafe_b64decode(s + '=' * (4 - len(s) % 4) if len(s) % 4 else s)
# Decode JWT payload
import json
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIn0.xxx"
payload_b64 = token.split('.')[1]
payload = json.loads(decode_base64url(payload_b64))
print(payload) # {'sub': 'user123'}
Go:
// Use RawURLEncoding for unpadded Base64url (e.g. JWT segments)
data, err := base64.RawURLEncoding.DecodeString(segment)
PHP:
function decodeBase64url(string $str): string {
$base64 = strtr($str, '-_', '+/');
return base64_decode(str_pad($base64, strlen($base64) + (4 - strlen($base64) % 4) % 4, '='));
}
Quick reference: common errors and fixes
| Error | Cause | Fix |
|---|---|---|
DOMException: Invalid character (JS atob) |
Whitespace or non-Base64 chars in input | str.replace(/\s+/g, '') before decoding |
binascii.Error: Incorrect padding (Python) |
Missing = padding |
Pad to multiple of 4: s + '=' * (-len(s) % 4) |
illegal base64 data (Go) |
Wrong encoding variant | Use URLEncoding for Base64url; RawStdEncoding for unpadded |
base64_decode() returns false (PHP) |
Invalid characters with strict: true |
Validate input or use without strict mode |
| Output is garbled binary | Input is a binary file, not text | Save bytes to file instead of decoding to string |
| Wrong characters decoded | Mixed Standard/URL-safe Base64 | Check whether input uses +/ or -_ |
Decoding Base64 on the command line
macOS / Linux:
# Decode a string
echo "SGVsbG8=" | base64 -d
# Hello
# Decode a file
base64 -d encoded.txt > decoded.bin
Windows PowerShell:
$encoded = "SGVsbG8="
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))
# Hello
Six common mistakes
1. Assuming decoded output is always text. Base64 often encodes binary data — images, PDFs, audio files. Treating the decoded bytes as UTF-8 text will produce garbage or a UnicodeDecodeError. Check the source of the Base64 string and decode to bytes first, then interpret appropriately.
2. Using atob() in Node.js. atob() is a browser API. It's available in Node.js v16+ but the idiomatic Node.js way is Buffer.from(str, 'base64').toString(). Using atob() in Node avoids an import but can create subtle issues with non-ASCII content.
3. Forgetting padding when handling JWTs. JWT segments deliberately omit padding. Feeding them directly to a standard decoder will error. Always add padding before decoding JWT segments.
4. Decoding Base64url with a standard decoder. If your Base64 string came from a URL, OAuth token, or JWT, it uses - and _. A standard decoder will reject or misread these characters. Use the URL-safe variant of the decoder.
5. Double-decoding. If a value has been Base64-encoded twice (common in some API integration layers), you'll decode to another Base64 string rather than the final value. If the output still looks like Base64, try decoding again.
6. Treating base64 -d output as UTF-8 on all terminals. The terminal may display binary bytes as question marks or replacement characters. Pipe to hexdump -C or xxd to inspect raw bytes without encoding assumptions.
FAQ
Q: How do I know if a string is Base64?
Look at the character set: uppercase letters, lowercase letters, digits 0–9, +, /, and = for padding (or -, _ without padding for Base64url). A properly padded Base64 string has a length divisible by 4. You can also try decoding it — if you get readable text or a valid file header, it was Base64.
Q: Is Base64 decoding the same as decryption? No. Base64 is a reversible encoding, not encryption. Anyone who sees a Base64 string can decode it with a single function call. Never use Base64 to protect sensitive data. It provides zero confidentiality.
Q: Why does my decoded Base64 look like binary garbage? The original data is binary (an image, PDF, executable, etc.), not plain text. Decode to bytes and save to a file with the appropriate extension rather than trying to print the result as a string.
Q: What's the difference between atob() and Buffer.from(..., 'base64')?
Both decode Base64, but atob() is a browser API that predates Node.js and operates on binary strings (one character per byte). Buffer.from() works with Node.js buffers and handles encoding more explicitly. In modern Node.js (v16+), both work, but Buffer.from() is more idiomatic for server-side code.
Q: How do I decode Base64 without importing a library?
In JavaScript (browser or modern Node.js), atob() is built-in. In Python, import base64 is from the standard library. In Go, encoding/base64 is standard. In PHP, base64_decode() is a built-in function. You never need a third-party package for basic Base64 decoding.
Q: Can I decode Base64 directly in the browser developer tools?
Yes. Open the browser console and run atob("your_base64_string_here"). For Base64url (from a JWT), replace - with + and _ with / first: atob("eyJhbGci...".replace(/-/g,'+').replace(/_/g,'/')).