Base64 vs Base64URL
A developer's guide to understanding when and why to use standard Base64 versus the URL-safe variant.
Two Variants, One Purpose
Base64 encoding comes in two primary variants: the standard Base64 encoding defined in RFC 4648 Section 4, and the URL-safe Base64URL encoding defined in RFC 4648 Section 5. Both serve the same fundamental purpose of converting binary data to text, but they differ in their character sets and padding behavior, and those differences matter significantly depending on your use case.
If you have ever wondered why a JWT token contains hyphens and underscores instead of plus signs and slashes, or why some Base64 strings end with = characters while others do not, this article will provide the complete answer.
The Character Differences
Standard Base64 and Base64URL share 62 of their 64 characters. They differ in exactly two characters and in their handling of padding:
Comparison Table
| Feature | Standard Base64 | Base64URL |
|---|---|---|
| RFC | RFC 4648, Section 4 | RFC 4648, Section 5 |
| Index 62 character | + (plus) | - (hyphen) |
| Index 63 character | / (slash) | _ (underscore) |
| Padding | Required (=) | Typically omitted |
| URL safe? | No (requires percent-encoding) | Yes |
| Filename safe? | No (/ is a path separator) | Yes |
Why Standard Base64 Breaks in URLs
The two characters that distinguish standard Base64 from Base64URL, + and /, both have special meanings in URLs:
- The plus sign (+) is interpreted as a space character in URL query strings. If you pass
a+bin a query parameter, the server receivesa b. This means any Base64 string containing+will be corrupted when used in a URL without percent-encoding. - The forward slash (/) is the URL path separator. Including a
/in a query parameter or path segment requires percent-encoding as%2F, which increases the string length and adds complexity. - The equals sign (=) is the query parameter assignment operator. While
=can appear in parameter values, it can cause parsing ambiguity and requires careful handling. Some URL parsers will misinterpret trailing=characters.
Base64URL solves all three problems by replacing + with -, replacing / with _, and omitting the = padding. The resulting string can be placed directly in any part of a URL without modification or encoding.
A Concrete Example
Consider encoding the byte sequence [0xFF, 0xFF, 0xFE] in both variants:
Input bytes: 0xFF 0xFF 0xFE
Binary: 11111111 11111111 11111110
6-bit groups: 111111 111111 111111 111110
Standard Base64: ///+
Base64URL: ___-
In a URL query parameter:
Standard: ?data=%2F%2F%2F%2B (percent-encoded, 20 chars)
Base64URL: ?data=___- (as-is, 10 chars)The standard version requires percent-encoding that doubles the length and makes the URL harder to read. The Base64URL version works directly, exactly as typed.
Padding: Required or Omitted?
In standard Base64, padding with = characters is required to ensure the encoded output length is always a multiple of 4. This makes it possible for a decoder to determine the exact number of bytes in the original input without any additional context.
Base64URL typically omits padding because the missing bytes can be inferred from the output length. If the encoded string length modulo 4 is 2, one padding character was removed and the original input had 1 byte remaining. If it is 3, two padding characters were removed and 2 bytes remained. A length divisible by 4 means no padding was needed.
// Standard Base64 (with padding)
encode("A") → "QQ==" (2 padding chars)
encode("AB") → "QUI=" (1 padding char)
encode("ABC") → "QUJD" (no padding)
// Base64URL (without padding)
encode("A") → "QQ" (no padding)
encode("AB") → "QUI" (no padding)
encode("ABC") → "QUJD" (same as standard)It is important to note that while most Base64URL implementations omit padding, some include it. The RFC states that padding "may be omitted." Robust decoders should handle both padded and unpadded input regardless of variant.
Converting Between Variants
Converting between standard Base64 and Base64URL is straightforward since the encoding logic is identical. You only need to swap the two different characters and handle padding:
// JavaScript: Standard Base64 → Base64URL
function toBase64URL(base64: string): string {
return base64
.replace(/\+/g, '-') // Replace + with -
.replace(/\//g, '_') // Replace / with _
.replace(/=+$/, ''); // Remove trailing padding
}
// JavaScript: Base64URL → Standard Base64
function fromBase64URL(base64url: string): string {
let base64 = base64url
.replace(/-/g, '+') // Replace - with +
.replace(/_/g, '/'); // Replace _ with /
// Add padding if needed
const remainder = base64.length % 4;
if (remainder === 2) base64 += '==';
if (remainder === 3) base64 += '=';
return base64;
}
// Python
import base64
# Standard → URL-safe
url_safe = base64.urlsafe_b64encode(data).rstrip(b'=')
# URL-safe → Standard
padded = url_safe + b'=' * (4 - len(url_safe) % 4)
standard = base64.urlsafe_b64decode(padded)When to Use Each Variant
Use Standard Base64 When:
- Email (MIME): The MIME standard specifically requires standard Base64. All email clients and servers expect the standard alphabet with padding.
- XML documents: The XML Schema specification references standard Base64 for
xs:base64Binarytypes. SOAP messages and XML-based protocols use this variant. - PEM certificates: SSL/TLS certificates in PEM format use standard Base64 between
-----BEGIN CERTIFICATE-----and-----END CERTIFICATE-----headers. - Data URIs: The
data:URI scheme uses standard Base64 encoding. Data URIs in HTML and CSS expect the standard alphabet with padding. - Database storage: When storing binary data in text columns, standard Base64 with padding is the conventional choice because the consistent output length simplifies validation and storage estimation.
Use Base64URL When:
- JSON Web Tokens (JWT): The JWT specification (RFC 7519) mandates Base64URL encoding for all three segments (header, payload, signature). Using standard Base64 in JWTs is incorrect and will cause interoperability issues. Explore JWT tokens with our JWT Decoder.
- URL query parameters: Any data embedded in URLs should use Base64URL to avoid the need for percent-encoding and to prevent data corruption from URL parsers.
- Filenames: Standard Base64 includes
/, which is a path separator on Unix/Linux/macOS and cannot appear in filenames. Base64URL uses_instead, making it safe for filenames across all operating systems. - Cookie values: HTTP cookies have restrictions on allowed characters. Base64URL produces values that are safe to use directly in cookies without additional encoding.
- OAuth and OIDC: OAuth 2.0 tokens, PKCE code challenges, and OpenID Connect ID tokens all use Base64URL encoding.
- Content-addressable storage: Systems that use hashes as identifiers (Git, IPFS, container registries) often Base64URL-encode their hashes for safe use in URLs and filenames.
Common Mistakes
Mixing up Base64 and Base64URL is a surprisingly common source of bugs. Here are the pitfalls to watch for:
Mistake 1: Using Standard Base64 in JWTs
// WRONG: Using standard Base64 for JWT segments
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
// May contain + or / characters that break token parsing
// CORRECT: Use Base64URL
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');Mistake 2: Forgetting to Restore Padding Before Decoding
// WRONG: Directly decoding Base64URL without restoring padding
const decoded = atob(base64urlString);
// May fail with "Invalid character" error due to - and _
// CORRECT: Convert to standard Base64 first
const base64 = base64urlString
.replace(/-/g, '+')
.replace(/_/g, '/')
+ '='.repeat((4 - base64urlString.length % 4) % 4);
const decoded = atob(base64);Mistake 3: Double-Encoding in URLs
// WRONG: Percent-encoding an already URL-safe string
const url = `/api?data=${encodeURIComponent(base64urlString)}`;
// Unnecessary - Base64URL is already URL-safe
// CORRECT: Use Base64URL strings directly in URLs
const url = `/api?data=${base64urlString}`;Language-Specific Support
Most programming languages provide dedicated APIs for both variants:
// JavaScript (Node.js 16+)
Buffer.from(data).toString('base64'); // Standard
Buffer.from(data).toString('base64url'); // Base64URL
# Python
import base64
base64.b64encode(data) # Standard
base64.urlsafe_b64encode(data) # Base64URL
// Java
Base64.getEncoder().encode(data); // Standard
Base64.getUrlEncoder().encode(data); // Base64URL
Base64.getUrlEncoder().withoutPadding() // Base64URL, no padding
.encode(data);
// Go
base64.StdEncoding.EncodeToString(data) // Standard
base64.URLEncoding.EncodeToString(data) // Base64URL (with padding)
base64.RawURLEncoding.EncodeToString(data) // Base64URL (no padding)
// Rust (base64 crate)
base64::engine::general_purpose::STANDARD.encode(data) // Standard
base64::engine::general_purpose::URL_SAFE.encode(data) // Base64URL
// C# / .NET
Convert.ToBase64String(data); // Standard only
// No built-in Base64URL; use manual replacement or a librarySummary: A Quick Decision Guide
When choosing between Base64 and Base64URL, ask yourself: "Will this encoded string appear in a URL, filename, cookie, or JWT?" If the answer is yes, use Base64URL. If the data will be stored in a database, transmitted via email, embedded in XML, or used in a data URI, use standard Base64. When in doubt, Base64URL is the safer default for modern web applications because it works in more contexts without additional encoding.
Try both variants side by side with our Base64 Encoder/Decoder tool, which supports switching between standard and URL-safe modes with a single click.
Further Reading
- RFC 4648 — Base Encodings
IETF standard defining Base64, Base64url, Base32, and Base16 encodings.
- RFC 7515 — JSON Web Signature (base64url)
How base64url encoding is used in JWS and JWT tokens.
- MDN btoa() and atob()
Web API reference for Base64 encoding and decoding in browsers.