Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Base64 vs Base64url

On this page
  1. At a glance
  2. What changes
  3. Why two variants exist
  4. Where each is the right choice
    1. Use standard base64 when:
    2. Use base64url when:
  5. Converting between them
  6. Padding: keep it or strip it?
  7. Same input, both outputs
  8. Languages that distinguish them
  9. Common mistakes
  10. Tools on this site

TL;DR. Standard base64 (+, /) is the historical default. Base64url (-, _) replaces the two URL-unsafe characters so the output can sit in a URL or filename without escaping. Both encode the same bytes and share one padding rule (RFC 4648 §3.2). Only the surface alphabet differs.

At a glance

Standard base64Base64url (RFC 4648 §5)
Position 62+-
Position 63/_
Padding (=)Included unless the referring spec says otherwise (RFC 4648 §3.2)Same rule (§3.2). JWS omits it (RFC 7515)
URL-safe out of the boxNoYes
MIME / emailStandardNo
JWTNoStandard
FilenamesRiskySafe
SpecRFC 4648 §4RFC 4648 §5

What changes

The 64-character alphabet differs in the last two characters:

Standard:  ABC...XYZabc...xyz0123456789+/
Base64url: ABC...XYZabc...xyz0123456789-_

That’s it. Positions 62 and 63 swap from URL-reserved characters to URL-safe ones. Everything else (positions 0–61) is identical.

Padding is not part of the difference. RFC 4648 §3.2 sets one padding rule for both alphabets, covered below.

Why two variants exist

When base64 was specified for email (RFC 989, 1987), the alphabet was chosen to maximize compatibility with the 7-bit ASCII-only mail infrastructure of the time. URLs weren’t a consideration.

When the web came along, putting base64 in URL parameters became common — but + and / are URL-reserved, so the encoded value had to be URL-encoded again (+ → %2B, / → %2F). That’s wasteful and error-prone, especially for tokens that pass through multiple systems.

RFC 4648 (2006) standardized base64url to fix this without breaking the existing standard.

Where each is the right choice

Use standard base64 when:

Use base64url when:

Converting between them

A two-line conversion in any language:

// Standard → URL-safe
const urlSafe = standard.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

// URL-safe → Standard
const padded = urlSafe + "=".repeat((4 - urlSafe.length % 4) % 4);
const standard = padded.replace(/-/g, "+").replace(/_/g, "/");

The bytes encoded are identical; only the surface alphabet differs.

Padding: keep it or strip it?

Padding (=) makes the length a multiple of 4. RFC 4648 §3.2 says implementations must include that padding unless the referring specification says otherwise. §5 only notes that = may be skipped when the data length is known implicitly. For signed JWTs (JWS), RFC 7515 is the spec that says to omit the trailing =. Other systems may insist on it.

  1. Standard base64 — keep padding. Many decoders reject unpadded input.
  2. Signed JWT (JWS) — RFC 7515 says to omit the trailing =.
  3. Base64url otherwise — include = unless the referring specification says otherwise, or the length is known implicitly. The decoder accepts both.

If you’re consuming someone else’s base64url output and the decoder fails with “invalid length,” try adding = until the length is a multiple of 4.

Same input, both outputs

Original bytes:  [0xff, 0xfb, 0xff, 0xfb] (4 bytes that exercise + and /)
Standard:        "//v/+w=="
Base64url:       "__v_-w"   (no padding)
Base64url:       "__v_-w=="  (padding kept)

Notice how the standard output uses both + and /, while the URL-safe version uses _ and -. Same data, different alphabet.

Languages that distinguish them

Most modern standard libraries provide both:

// Node.js (16+)
Buffer.from(text).toString("base64");
Buffer.from(text).toString("base64url");

// Python
import base64
base64.b64encode(data)             // standard
base64.urlsafe_b64encode(data)     // URL-safe

// Go
import "encoding/base64"
base64.StdEncoding.EncodeToString(data)         // standard
base64.URLEncoding.EncodeToString(data)         // URL-safe with padding
base64.RawURLEncoding.EncodeToString(data)      // URL-safe without padding

// Rust (base64 crate)
use base64::{Engine, engine::general_purpose};
general_purpose::STANDARD.encode(data)
general_purpose::URL_SAFE_NO_PAD.encode(data)

When the library has both, pick the right one explicitly. Mixing silently leads to the “works in dev, breaks in prod” pattern.

Common mistakes

Tools on this site