How to Decode Base64 Strings (and JWT Payloads)
Base64 is reversible encoding, not encryption. Decode it in one step, handle Unicode correctly, and read JWT sections that use Base64url.
What Base64 Actually Is
Per RFC 4648, Base64 maps arbitrary binary data to 64 printable ASCII characters (A-Z, a-z, 0-9, +, /) using 3-byte → 4-character groups, with = padding to fill out the final group. It is fully reversible with no key, so it is encoding, not encryption — never use it to hide secrets. See also the Base64 encoding is not encryption blog post for the deeper explainer.
The Base64 tool does the encode / decode round trip in your browser.
Decoding and Encoding
In the browser, use btoa to encode and atob to decode — but both only handle Latin-1 bytes. For non-ASCII text, round-trip through UTF-8 so characters like é, ü, or 你 survive:
// Encode UTF-8 string → Base64
const encoded = btoa(
String.fromCharCode(...new TextEncoder().encode('Ada Lovelace 你好'))
);
// → "QWRhIExvdmVsYWNlIOS9oOWlvQ=="
// Decode Base64 → UTF-8 string
const decoded = new TextDecoder().decode(
Uint8Array.from(atob(encoded), c => c.charCodeAt(0))
);
// → "Ada Lovelace 你好"
Without the TextEncoder / TextDecoder round trip, multi-byte characters get garbled — atob interprets every Base64-decoded byte as a Latin-1 codepoint, which is wrong for anything outside ASCII.
Base64 vs Base64url
Two variants of the same encoding, picked for different contexts:
| Feature | Standard Base64 | Base64url |
|---|---|---|
| Spec | RFC 4648 §4 | RFC 4648 §5 |
Character 62 |
+ |
- |
Character 63 |
/ |
_ |
| Padding | = (required) |
Often omitted |
| Safe in URLs / filenames | No (+ and / need escaping) |
Yes |
| Used by | MIME, email attachments | JWT, OAuth tokens, JWS / JWE |
atob does not accept Base64url directly — convert first by replacing - → +, _ → /, and padding the length up to a multiple of 4 with =:
function base64urlToBase64(input) {
let b64 = input.replace(/-/g, '+').replace(/_/g, '/');
while (b64.length % 4) b64 += '=';
return b64;
}
Reading a JWT
A JWT (per RFC 7519) is three Base64url sections joined by dots: header, payload, and signature:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBZGEiLCJpYXQiOjE3MTY4MDAwMDB9.cF8wq...
└──────── header ───────────┘ └─────────── payload ────────────┘ └─ signature ─┘
Decoding the first two sections reveals the claims; the signature is binary and is not meant to be human-readable. The JWT decode tool does the split + decode in one click. In code:
function decodeJwt(token) {
const [headerB64, payloadB64] = token.split('.');
const header = JSON.parse(atob(base64urlToBase64(headerB64)));
const payload = JSON.parse(atob(base64urlToBase64(payloadB64)));
return { header, payload };
}
For the deeper walk-through, see the How to decode a JWT blog post.
Decoding Is Not the Same as Verifying
Decoding a JWT reveals the claims but says nothing about authenticity. Anyone can forge a token whose payload says "role":"admin"; only the signature, verified against the issuer's key, proves the token is real. Never make a trust decision on an unverified token — decode for inspection, debugging, and display only.
Common JWT Claims
The registered claims defined by RFC 7519 §4.1 — these names are reserved across all JWT-using systems:
| Claim | Meaning | Notes |
|---|---|---|
iss |
Issuer | The system that minted the token |
sub |
Subject | Usually the user ID |
aud |
Audience | The intended recipient — always check this server-side to prevent token reuse across services |
exp |
Expiration | Unix seconds — always check before trusting any claim |
nbf |
Not Before | Unix seconds — token is invalid until this time |
iat |
Issued At | Unix seconds when the token was minted |
jti |
JWT ID | Unique identifier for revocation lists |
Custom claims live alongside these (e.g. role, tenant_id). Always validate exp and aud on the server, not just on the client.
See also
This guide covers Base64 + JWT decoding. For the JWT-specific tool, see JWT Decode; for the deeper "why Base64 isn't security" explainer, see Base64 encoding is not encryption.
Sources
- RFC 4648 — The Base16, Base32, and Base64 Data Encodings
- RFC 7519 — JSON Web Token (JWT)
- MDN —
btoaandatob(the browser Base64 primitives, ASCII-only) - MDN —
TextEncoder(the UTF-8 round-trip helper)
Last reviewed June 2026.
JSON repair guides
Topic hubs
- JSON Parse Errors: Read the Message, Jump to the Fix
- Fix Invalid JSON: From 'What's Wrong' to a Clean File
- JSON Formatter, Validator, Viewer: Pick the Right Tool
- Repair LLM JSON Output: Handling Almost-JSON from AI
- Privacy: JSON Tools That Don't Leave Your Browser
- JSON Interop: YAML, CSV, XML, JWT, Schema
Specific guides
- URL Encoding: Percent-Encode Query Parameters and Paths
- Convert YAML to JSON (and Avoid Indentation Errors)
- Convert JSON to CSV: Flatten an Array of Objects
- Convert JSON to XML: Root Elements, Attributes, and Arrays
- Escape JSON as a String Literal (and Decode Double-Encoded JSON)
- Fix Trailing Comma in JSON
- Fix Single Quotes in JSON
- Fix Unquoted Keys in JSON
- Repair LLM JSON Output
- Fix JSON Parse Error: Expected Property Name
- JSON vs JS Object Literal: The Key Differences
- Validate JSON Before API Requests
- JSON Formatter vs JSON Repair
- Fix JSON Unexpected Token Errors
- JSON to JavaScript Object Converter