Base64URL is Base64 with three changes: + becomes -, / becomes _, and = padding is omitted. Those three substitutions make the encoded string safe for use in URLs, filenames, and HTTP headers without any percent-encoding.

Here's the full comparison at a glance:

Property Standard Base64 Base64URL
Character + Used Replaced with -
Character / Used Replaced with _
Padding = Required Omitted
URL safe No Yes
Primary use MIME email, data URIs, config files JWTs, OAuth tokens, WebAuthn, URL params
RFC RFC 4648 §4 RFC 4648 §5

To encode or decode either variant, use the free Base64 encoder/decoder. It handles both standard and URL-safe variants.

Why Base64URL Exists

Standard Base64 uses a 64-character alphabet that includes three characters that cause problems in URLs:

  • +: interpreted as a space in URL query strings (?q=hello+world decodes to "hello world")
  • /: treated as a path separator, breaking routing and file paths
  • =: used as the delimiter in key=value query parameters, mangling encoded data when it appears mid-string

Put standard Base64 output into a URL query parameter without percent-encoding it first, and the decoded result will be wrong. The + signs become spaces, the / characters break the URL path, and the = padding collides with parameter syntax.

The fix, defined in RFC 4648 §5, is a modified alphabet that replaces those three characters with URL-safe alternatives. The encoding algorithm is identical; only three characters in the output alphabet change.

What Actually Changes in the Character Set

The full Base64URL alphabet is: A-Z, a-z, 0-9, -, _

That's 64 characters, same as standard Base64. Only the last two symbol slots differ:

Standard Base64 Base64URL Why
+ (char 62) - Safe in URLs and filenames
/ (char 63) _ Safe in URLs and filenames
= (padding) omitted = has meaning in query strings

A valid Base64URL string contains only [A-Za-z0-9_-]. Validation regex: ^[A-Za-z0-9_\-]*$

Padding: The Part That Trips People Up

This is where most Base64URL bugs originate.

Standard Base64 always pads output with = to make the length a multiple of 4. If your input encodes to 10 characters, you get == appended to reach 12.

Base64URL omits this padding by default. RFC 4648 §3.2 explicitly permits implementations to omit padding, and most Base64URL encoders do.

The padding formula — when you need to add padding back for a decoder that requires it:

remainder = len(base64url_string) % 4

if remainder == 0: no padding needed
if remainder == 2: add ==
if remainder == 3: add =
if remainder == 1: malformed input (error)

In code:

// JavaScript: add padding to a Base64URL string before passing to atob()
function addPadding(base64url) {
  const pad = base64url.length % 4;
  if (pad === 1) throw new Error("Invalid Base64URL length");
  return base64url + "=".repeat(pad === 0 ? 0 : 4 - pad);
}

The atob() gotcha: The browser-native atob() function decodes standard Base64 only. It does not handle Base64URL. If you call atob(jwtSegment), it throws InvalidCharacterError the moment the segment contains - or _. This is the most common JWT-decoding bug in frontend code.

When to Use Each

Use standard Base64 when:

  • MIME email: Base64 is the standard encoding for binary attachments in email (RFC 2045)
  • Data URIs: CSS and HTML data: URIs use standard Base64 — data:image/png;base64,iVBORw...
  • XML and JSON storage: Embedding binary blobs in text formats where URL safety doesn't matter
  • Config files: Storing binary credentials or keys in plain-text config

Use Base64URL when:

  • JWTs: RFC 7515 mandates Base64URL encoding without padding for all JWT segments (header, payload, signature)
  • OAuth 2.0: Authorization codes, tokens, and state parameters use Base64URL
  • WebAuthn / FIDO2: Challenge-response data in the Web Authentication API uses Base64URL
  • URL query parameters: Any binary data that needs to survive round-trips through URLs
  • Filenames: When encoded data becomes part of a filename (Base64URL characters are filesystem-safe)

The JWT rule is absolute: RFC 7515 §2 defines Base64url encoding as "Base64 encoding using the URL- and filename-safe character set... with all trailing '=' characters omitted." JWTs that include = padding are non-conformant and should be rejected by spec-compliant libraries.

Code Examples

JavaScript

// JavaScript — Base64URL encode/decode (browser and Node.js)

// Encode to Base64URL
function base64UrlEncode(str) {
  return btoa(str)
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

// Decode from Base64URL
function base64UrlDecode(str) {
  // Replace URL-safe chars back to standard Base64
  str = str.replace(/-/g, "+").replace(/_/g, "/");
  // Add padding if needed
  const pad = str.length % 4;
  if (pad === 2) str += "==";
  if (pad === 3) str += "=";
  return atob(str);
}

console.log(base64UrlEncode("Hello, World!"));  // SGVsbG8sIFdvcmxkIQ
console.log(base64UrlDecode("SGVsbG8sIFdvcmxkIQ"));  // Hello, World!

// Decode a JWT payload segment
function decodeJwtPayload(token) {
  const payload = token.split(".")[1];
  return JSON.parse(base64UrlDecode(payload));
}

Python

# Python 3 — Base64URL encode/decode
import base64

# Encode to Base64URL
def base64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("utf-8")

# Decode from Base64URL (handles missing padding)
def base64url_decode(s: str) -> bytes:
    # Add padding back
    pad = len(s) % 4
    if pad == 2:
        s += "=="
    elif pad == 3:
        s += "="
    return base64.urlsafe_b64decode(s)

print(base64url_encode(b"Hello, World!"))   # SGVsbG8sIFdvcmxkIQ
print(base64url_decode("SGVsbG8sIFdvcmxkIQ"))  # b'Hello, World!'

Go

// Go — Base64URL encode/decode
package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    // Encode to Base64URL (no padding)
    encoded := base64.RawURLEncoding.EncodeToString([]byte("Hello, World!"))
    fmt.Println(encoded) // SGVsbG8sIFdvcmxkIQ

    // Decode from Base64URL (no padding)
    decoded, err := base64.RawURLEncoding.DecodeString(encoded)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(decoded)) // Hello, World!
}
// Note: base64.RawURLEncoding = Base64URL without padding (JWT-compatible)
// base64.URLEncoding = Base64URL with padding

Go's standard library distinguishes these explicitly. base64.RawURLEncoding is Base64URL without padding (what JWTs use). base64.StdEncoding is standard Base64 with = padding.

Common Mistakes

1. Using atob() on JWT segments
atob() is standard Base64 only. JWT segments are Base64URL. Replace - with + and _ with /, add padding, then call atob() — or use a JWT library like jwt-decode.

2. Forgetting to strip padding before inserting into a URL
If your encoder adds = padding, those characters become %3D when percent-encoded in a URL. Strip trailing = before embedding Base64URL in URLs or filenames.

3. Expecting padding to always be absent
Some systems — including AWS ALB when used with Cognito — emit Base64URL-encoded values with = padding. RFC 7515 says strip it, but you may encounter it in production. Write decoders that handle both padded and unpadded input.

4. Treating Base64URL as encryption
Neither variant is encryption. A Base64URL-encoded JWT payload is trivially decodable by anyone. The signature verifies integrity, not confidentiality. Don't embed secrets in JWT payloads unless the token is also encrypted (JWE).

Frequently asked questions

Is Base64URL the same as Base64?

No. Base64URL is a variant that replaces + with -, / with _, and omits = padding. The encoding algorithm is identical; only the output alphabet differs. You cannot mix the two without conversion.

Do I need to add padding when decoding Base64URL?

It depends on the decoder. Most Base64URL-aware decoders handle unpadded input natively. Standard Base64 decoders (like atob()) require padding. Use the formula: length % 4 == 2 → add ==; length % 4 == 3 → add =.

Why does my JWT fail to decode with atob()?

JWT segments are Base64URL-encoded. atob() expects standard Base64. The - and _ characters in Base64URL are invalid in standard Base64, so atob() throws InvalidCharacterError. Replace -+ and _/, add padding, then call atob(). Or use a dedicated JWT library.

Which does JWT use — Base64 or Base64URL?

Base64URL, without padding. RFC 7515 §2 is explicit: JWT uses "base64url encoding... with all trailing '=' characters omitted." A conformant JWT library never produces or accepts = in the header, payload, or signature segments.

Can I convert between Base64 and Base64URL?

Yes. Standard Base64 → Base64URL: replace + with -, / with _, strip =. Base64URL → Standard Base64: replace - with +, _ with /, add padding using the length % 4 formula.

Summary

Standard Base64 and Base64URL produce equivalent encoded data. Base64URL swaps +/- and //_ and drops = padding to make the output safe for URLs, filenames, and HTTP headers. Use standard Base64 for MIME email and data URIs. Use Base64URL for JWTs, OAuth tokens, WebAuthn, and anywhere the encoded string appears in a URL or filename.

To encode and decode both variants in your browser — no sign-up, no server-side processing — use the Base64 encoder/decoder at developertools.app.