Skip to content

Base64 Encode / Decode

Base64 encodes arbitrary bytes as 64 printable ASCII characters so they survive systems that only handle text — email bodies, JSON fields, data URIs, HTTP headers. This tool encodes and decodes correctly for Unicode input, supports the URL-safe alphabet, and reports a precise error instead of silently producing mojibake.

Type or paste on the left; the conversion runs as you type, entirely in your browser.

How the encoding works

Base64 takes three input bytes — 24 bits — and re-splits them into four 6-bit groups, each of which indexes a 64-character alphabet: A–Z, a–z, 0–9, +, /. Three bytes in, four characters out, which is why Base64 output is always about 33% larger than its input. When the input length is not a multiple of three, the final group is padded with one or two = characters so that the total length stays a multiple of four.

That expansion is the cost of the guarantee. Every output character is in the ASCII printable range, contains no whitespace, no control characters, and nothing a mail transfer agent or an XML parser will mangle. It is a transport encoding, not compression and emphatically not encryption — the alphabet is public and decoding needs no key.

Base64 provides zero confidentiality. HTTP Basic auth credentials, JWT payloads, and Kubernetes Secrets are all Base64 and all readable by anyone who can see them. If it needs to be secret, it needs to be encrypted; Base64 only makes it survive transport.

Standard vs URL-safe (base64url)

The standard alphabet's last two characters, + and /, are both meaningful inside a URL — + means a space in a query string and / is a path separator. RFC 4648 §5 therefore defines base64url, which substitutes - and _ and usually drops the = padding, since = also needs escaping. The underlying bytes are identical; only the rendering differs.

Read the full explanation of base64 encode / decode results ↓
Standard (RFC 4648 §4)URL-safe (§5)
Index 62+-
Index 63/_
Padding= requiredUsually omitted
Used byMIME, HTTP Basic, PEM, Kubernetes SecretsJWT, OAuth PKCE, WebAuthn, URL parameters

This tool detects - or _ in input you are decoding and switches alphabets automatically, and it restores missing padding before decoding. That is why a JWT segment pasted here decodes without you having to fix it up first — though the JWT decoder will split the token and format the claims for you as well.

The Unicode problem most Base64 tools get wrong

JavaScript's built-in btoa() operates on a "binary string" — one character per byte — and throws InvalidCharacterError on any code point above U+00FF. Pass it café and it works; pass it 日本語 or an emoji and it fails outright. The workaround that circulated for years, btoa(unescape(encodeURIComponent(str))), works but relies on unescape(), which has been deprecated for a decade.

The correct approach, and the one used here, is to convert explicitly: new TextEncoder().encode(str) produces the UTF-8 bytes, and those bytes are then Base64-encoded. Decoding reverses it with TextDecoder. This matters practically — if you encode text here and decode it in Python with base64.b64decode(s).decode("utf-8"), you get exactly the original string back, because both sides agree that the underlying bytes are UTF-8.

function encodeBase64(text) {
  const bytes = new TextEncoder().encode(text)
  let binary = ''
  for (const b of bytes) binary += String.fromCharCode(b)
  return btoa(binary)
}

function decodeBase64(b64) {
  const binary = atob(b64)
  const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0))
  // fatal: true throws on invalid UTF-8 instead of inserting U+FFFD
  return new TextDecoder('utf-8', { fatal: true }).decode(bytes)
}

// URL-safe variants
const toUrlSafe = (s) => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
const fromUrlSafe = (s) => s.replace(/-/g, '+').replace(/_/g, '/')
UTF-8-safe Base64 in the browser

The fatal: true option is the difference between a tool that tells you the truth and one that does not. Without it, TextDecoder replaces invalid byte sequences with U+FFFD (�) and returns a string, so decoding a PNG produces plausible-looking garbage. With it, you get an exception, which this page turns into a message telling you the data decoded fine but is binary rather than text.

Command-line equivalents

# Encode — note printf, not echo, so no trailing newline is included
printf 'hello world' | base64
# -> aGVsbG8gd29ybGQ=

# Decode (GNU coreutils uses -d, macOS/BSD accepts -D or -d)
echo 'aGVsbG8gd29ybGQ=' | base64 -d

# openssl works everywhere; -A keeps it on one line
printf 'hello world' | openssl base64 -A
echo 'aGVsbG8gd29ybGQ=' | openssl base64 -d -A

# Long output wraps at 76 chars by default — disable it for a single line
base64 -w 0 certificate.der      # GNU
base64 -i certificate.der | tr -d '\n'   # macOS

# Read a Kubernetes secret (stored Base64, not encrypted)
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d

# Decode a JWT payload — base64url needs the alphabet translated first
echo "$JWT" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null
Encoding and decoding from the shell

The last line is worth keeping. tr '_-' '/+' converts base64url back to the standard alphabet, and the 2>/dev/null suppresses the padding complaint that some base64 builds emit when the input length is not a multiple of four.

Reading errors from this tool

  • "Not valid Base64." The input contains characters outside the alphabet. Usually a copy-paste that picked up a quotation mark, an ellipsis from truncated output, or a stray URL fragment. Whitespace and line breaks are stripped automatically, so they are never the cause.
  • "Invalid Base64 length." A Base64 string can never be one more than a multiple of four characters, because 6 bits cannot encode a whole byte. This means the value was truncated — a common outcome of copying from a terminal that wrapped, or from a database column that was too narrow.
  • "Decoded successfully but the bytes are not valid UTF-8." The decode worked; the result is binary. A PNG, a gzip stream, a DER-encoded certificate, or an encrypted blob all produce this. It is not an error in your input.
  • Output looks like `` or `é`. Double encoding, or a UTF-8/Latin-1 mismatch upstream. é is the two UTF-8 bytes of é interpreted as Latin-1 — the data was decoded once with the wrong charset before it reached you.

Where Base64 shows up in DNS and mail work

Base64 is everywhere in the infrastructure this site is about. DKIM public keys are Base64 inside a p= tag in a TXT record — an oversized RSA-2048 key is exactly why DKIM records so often need splitting across multiple 255-character strings; check yours with the DKIM checker. DNSSEC DNSKEY and RRSIG records carry Base64 key and signature material. TLSA and SSHFP use hex rather than Base64, which is a frequent source of paste errors between them.

PEM files are Base64 with a header and footer: -----BEGIN CERTIFICATE-----, 64-character lines of Base64-encoded DER, then the matching END line. If you strip the delimiters and the line breaks, what remains decodes here — though the result is binary DER, so you will get the "not valid UTF-8" message, which is the correct answer. Use openssl x509 -text to read it properly, or the SSL certificate checker to inspect what a live server presents.

A final practical note on size. Because Base64 inflates data by a third, embedding a large image as a data: URI costs you meaningfully more bytes over the wire than linking it, and it defeats caching because the image can no longer be cached separately from the document. It is a reasonable trick for icons of a few hundred bytes and a poor one for photographs.

JWT Decoder
Inspect JWT header and payload — locally, no signature check

Frequently asked questions

Is Base64 encryption?
No. It is a reversible encoding with a public alphabet and no key — anyone can decode it in one command. HTTP Basic auth headers, JWT payloads, and Kubernetes Secrets are all Base64 and all readable by anyone who obtains them.
Why does my emoji or accented text fail in other Base64 tools?
Because they call btoa() directly, which throws on any character above U+00FF. The fix is to convert to UTF-8 bytes with TextEncoder first, which is what this page does. Decoding here and in Python, Go, or Java gives byte-identical results.
What are the = signs at the end?
Padding. Base64 works in 3-byte blocks; when the input length is not a multiple of three, one or two = characters keep the output length a multiple of four. base64url normally drops them, and decoders that accept unpadded input add them back internally.
When should I use the URL-safe alphabet?
Whenever the value will appear in a URL path, query string, cookie, or filename. + and / from the standard alphabet both have meaning in those contexts and would need percent-encoding. JWTs, OAuth PKCE challenges, and WebAuthn all use base64url.
Why did decoding give me an error saying the result is not text?
Because it is not. The Base64 decoded correctly to bytes, but those bytes are binary — an image, a compressed stream, or a DER certificate. This tool tells you that rather than showing replacement characters and pretending it worked.
How much larger does Base64 make my data?
Exactly 4 output characters per 3 input bytes, so about 33% plus padding. A 1 MB image becomes roughly 1.37 MB as a data URI. That is why inlining large assets usually costs more than it saves.
Can I Base64-encode a whole file here?
This tool takes text input. For binary files use base64 -w 0 file on Linux or base64 -i file | tr -d '\n' on macOS — that also avoids loading a large file into a browser tab.
Why does my DKIM TXT record contain Base64 that is split into chunks?
A single DNS character-string is limited to 255 bytes, and a Base64-encoded RSA-2048 public key is longer than that. The record is published as multiple adjacent strings that resolvers concatenate. Quoting them incorrectly is one of the most common DKIM failures — the DKIM checker will show you what actually resolves.