Base64 vs Base64URL: What's the Difference?
Introduction
If you have worked with web APIs, JSON Web Tokens (JWT), or URL-safe data transmission, you have likely encountered both Base64 and Base64URL encoding. While they look nearly identical at first glance, the subtle differences between them are critical for correct implementation.
This guide explains exactly what sets Base64 and Base64URL apart, why those differences exist, and when to use each encoding scheme in your applications.
What is Standard Base64?
Base64 is a binary-to-text encoding scheme that converts binary data into an ASCII string format using a set of 64 characters. The standard Base64 alphabet consists of:
- Uppercase letters A–Z (26 characters)
- Lowercase letters a–z (26 characters)
- Digits 0–9 (10 characters)
- The plus sign
+(value 62) - The forward slash
/(value 63) - The equals sign
=(padding character)
Standard Base64 Example
const text = "Hello World";
const base64 = btoa(text);
console.log(base64); // SGVsbG8gV29ybGQ=
// With binary data
const binaryData = new Uint8Array([72, 101, 108, 108, 111]);
const base64Binary = btoa(String.fromCharCode(...binaryData));
console.log(base64Binary); // SGVsbG8=
What is Base64URL?
Base64URL is a URL-safe variant of Base64 encoding defined in RFC 4648. It modifies the standard Base64 alphabet to make the output safe for use in URLs, filenames, and HTML form values without requiring percent-encoding.
The Key Differences
| Character in Base64 | Character in Base64URL | Reason |
|---|---|---|
+ (plus) | - (hyphen) | + is meaningful in URLs (space encoding) |
/ (slash) | _ (underscore) | / is a path separator in URLs |
Padding = | Removed or optional | = can be percent-encoded in URLs |
Base64URL Example
function base64ToBase64Url(base64) {
return base64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
function base64UrlToBase64(base64url) {
let base64 = base64url
.replace(/-/g, '+')
.replace(/_/g, '/');
// Restore padding
while (base64.length % 4) {
base64 += '=';
}
return base64;
}
const text = "Hello World";
const standardBase64 = btoa(text); // SGVsbG8gV29ybGQ=
const base64url = base64ToBase64Url(standardBase64); // SGVsbG8gV29ybGQ
console.log(base64url);
Why Does This Matter?
The URL Problem
Standard Base64 uses + and / characters that have special meanings in URLs:
+is interpreted as a space in URL query strings (application/x-www-form-urlencoded)/is a path separator that can break URL routing=is sometimes percent-encoded as%3D, making URLs longer and less readable
When you include a standard Base64 string in a URL, you typically need to percent-encode these characters:
Standard Base64 in URL (problematic):
https://example.com/api/data?token=SGVsbG8gV29ybGQ=+abc/def
After percent-encoding (ugly):
https://example.com/api/data?token=SGVsbG8gV29ybGQ%3D%2Babc%2Fdef
With Base64URL, the same token becomes:
Base64URL in URL (clean):
https://example.com/api/data?token=SGVsbG8gV29ybGQ-abc_def
JWT: The Primary Use Case
JSON Web Tokens (JWT) use Base64URL encoding for all three parts (header, payload, signature). This is specified in RFC 7519:
// JWT structure: header.payload.signature
// Each part is Base64URL-encoded
const header = { alg: "HS256", typ: "JWT" };
const payload = { sub: "1234567890", name: "John Doe" };
const encodedHeader = base64ToBase64Url(btoa(JSON.stringify(header)));
const encodedPayload = base64ToBase64Url(btoa(JSON.stringify(payload)));
console.log(encodedHeader); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
console.log(encodedPayload); // eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
Using standard Base64 in JWT would cause parsing errors when the token is passed via URL or stored in cookies.
Padding: To Pad or Not to Pad?
Standard Base64 Padding
Standard Base64 always includes padding = characters to make the output length a multiple of 4:
btoa("A"); // "QQ==" (padded to 4 characters)
btoa("AB"); // "QUI=" (padded to 4 characters)
btoa("ABC"); // "QUJD" (already multiple of 3, no padding)
Base64URL Padding
Base64URL typically removes padding for URL safety. The decoding side must handle the missing padding:
function decodeBase64Url(base64url) {
// Add padding if necessary
const padded = base64url + '==='.slice((base64url.length + 3) % 4);
const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');
return atob(base64);
}
// Usage
const encoded = "SGVsbG8"; // No padding
const decoded = decodeBase64Url(encoded); // "Hello"
Note: Some implementations keep the padding in Base64URL. Always check what your target system expects.
When to Use Each Encoding
Use Standard Base64 When:
- Encoding binary data for email attachments (MIME)
- Storing binary data in XML or JSON (non-URL contexts)
- Encoding Data URIs for CSS or HTML (e.g.,
data:image/png;base64,...) - General-purpose encoding where the output won't be used in URLs
// Data URI example (uses standard Base64)
const canvas = document.getElementById('my-canvas');
const dataUrl = canvas.toDataURL(); // data:image/png;base64,iVBORw0KGgo...
Use Base64URL When:
- Encoding data for URL query parameters or path segments
- Working with JWT tokens
- Storing encoded data in cookies
- Generating filename-safe encoded strings
- Using OAuth 2.0 state parameters
// Storing state in OAuth flow (uses Base64URL)
const state = {
redirect: '/dashboard',
csrf: 'random-token-here'
};
const encodedState = base64ToBase64Url(
btoa(JSON.stringify(state))
);
// Use in redirect URL
const authUrl = `https://auth.example.com/authorize?state=${encodedState}`;
Implementation in Popular Languages
Node.js
const crypto = require('crypto');
// Standard Base64
const standard = Buffer.from('Hello World').toString('base64');
console.log(standard); // SGVsbG8gV29ybGQ=
// Base64URL
function toBase64Url(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
const urlSafe = toBase64Url(Buffer.from('Hello World'));
console.log(urlSafe); // SGVsbG8gV29ybGQ
Python
import base64
def to_base64url(data: bytes) -> str:
# Standard Base64
standard = base64.b64encode(data).decode('ascii')
# Convert to Base64URL
url_safe = standard.replace('+', '-').replace('/', '_').rstrip('=')
return url_safe
def from_base64url(base64url: str) -> bytes:
# Restore standard Base64
standard = base64url.replace('-', '+').replace('_', '/')
# Restore padding
padding = 4 - len(standard) % 4
if padding != 4:
standard += '=' * padding
return base64.b64decode(standard)
# Usage
encoded = to_base64url(b"Hello World")
print(encoded) # SGVsbG8gV29ybGQ
decoded = from_base64url(encoded)
print(decoded) # b'Hello World'
Go
package main
import (
"encoding/base64"
"strings"
)
func toBase64URL(data []byte) string {
b64 := base64.StdEncoding.EncodeToString(data)
return strings.TrimRight(
strings.ReplaceAll(
strings.ReplaceAll(b64, "+", "-"),
"/", "_",
),
"=",
)
}
func fromBase64URL(s string) ([]byte, error) {
s = strings.ReplaceAll(s, "-", "+")
s = strings.ReplaceAll(s, "_", "/")
// Add padding if needed
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.StdEncoding.DecodeString(s)
}
Common Pitfalls
1. Mixing Up the Two Encodings
// WRONG: Using standard Base64 in JWT
const jwtPart = btoa(JSON.stringify(payload)); // May contain +, /, =
// CORRECT: Use Base64URL for JWT
const jwtPart = base64ToBase64Url(btoa(JSON.stringify(payload)));
2. Forgetting to Handle Missing Padding
// WRONG: Assuming padding exists
const decoded = atob(base64urlString); // May fail if no padding
// CORRECT: Add padding before decoding
const padded = base64urlString + '==='.slice((base64urlString.length + 3) % 4);
const decoded = atob(padded.replace(/-/g, '+').replace(/_/g, '/'));
3. Double Encoding
// WRONG: Encoding already-encoded data
const doubleEncoded = base64ToBase64Url(base64ToBase64Url(standardBase64));
// CORRECT: Only encode once
const encoded = base64ToBase64Url(standardBase64);
Summary
| Feature | Base64 | Base64URL |
|---|---|---|
+ character | + | - |
/ character | / | _ |
| Padding | Required | Usually removed |
| URL-safe | No | Yes |
| JWT compatible | No | Yes |
| Data URI compatible | Yes | No |
Base64 and Base64URL serve different purposes. Choose Base64 for general encoding and Data URIs, and Base64URL whenever the encoded data will appear in URLs, JWTs, or other URL-sensitive contexts.
Related Tools
- Encode and decode Base64 strings with our Base64 Encoder/Decoder
- Convert between Base64 and Base64URL with our Base64URL Converter