Base64 vs Base64URL: what's the difference and when to use which

base64 encoding developer-tools

Base64 vs Base64URL: what's the difference and when to use which

If you have ever encoded a binary blob into a string, you have used Base64. If you have ever put that string into a URL, you have probably hit a bug and discovered Base64URL. The two are siblings — same algorithm, different alphabet — but the difference matters a lot in practice.

This guide covers the character-level differences, why + and / break URLs, how padding interacts with both, and a decision tree for picking the right variant.

Want to encode or decode either variant in your browser? Use the free Base64 Encoder/Decoder — handles UTF-8, files, and URL-safe mode.


What Base64 actually is

Base64 encodes any byte sequence into a string drawn from a 64-character alphabet. Every 3 input bytes become 4 output characters. The alphabet is defined in RFC 4648:

Standard Base64 alphabet:
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 + /

Base64URL alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 - _

The only difference is two characters:

PositionStandardURL-safe
62+-
63/_

That is it. The algorithm is identical. The encoding and decoding logic is identical. Only those two characters change.


Why the two characters matter

The standard alphabet uses + and /. Both have special meanings in URLs:

  • + in a query string is decoded as a space by most server frameworks.
  • / is the path separator — embedding it in a query parameter or path segment breaks routing.
  • Neither is safe to put in a URL without percent-encoding, which defeats the purpose of using Base64 in the first place.
Base64URL replaces them with - and _, both of which are URL-safe and need no escaping.

Example

Encode the bytes of Hello, World! in standard Base64:

SGVsbG8sIFdvcmxkIQ==

Now imagine putting that in a URL:

https://example.com/redirect?token=SGVsbG8sIFdvcmxkIQ==

The == padding is technically fine in a query string, but if the token contained + or /, the URL would break. For example, encoding the bytes ~??? gives fj8/Pw== in standard Base64 — the / characters would be misinterpreted as path separators.

In Base64URL, the same bytes give fj8_Pw==, which is safe to embed anywhere in a URL.


Padding: = vs no padding

Both variants use = for padding to make the output length a multiple of 4. Some URL-safe contexts strip the padding:

  • JWTs (JSON Web Tokens) strip = padding entirely.
  • Many URL parameter schemes strip it because = is the key-value separator in query strings.
If you strip padding, you must re-add it before decoding. The rule: pad with = until the length is a multiple of 4.

function restorePadding(str) {
  return str + '='.repeat((4 - (str.length % 4)) % 4);
}

The RuMystic Base64 tool handles this automatically when you toggle URL-safe mode.


The decision tree

Use this flowchart to pick the right variant:

  • Will the encoded string go into a URL, filename, or any context where + and / have special meaning?
  • - Yes → use Base64URL. - No → continue.
  • Is the encoded string going to be put into a JWT, JWS, or JWE?
  • - Yes → use Base64URL without padding (the JWT spec). - No → continue.
  • Are you interoperating with a system that expects standard Base64? (MIME email, data URIs, most legacy APIs)
  • - Yes → use standard Base64. - No → use standard Base64 as the default; it is what btoa() and Buffer.toString('base64') produce.


    Language-by-language reference

    JavaScript (browser)

    // Standard Base64
    const encoded = btoa('Hello, World!');  // SGVsbG8sIFdvcmxkIQ==
    const decoded = atob(encoded);

    // Base64URL (manual) const urlSafe = btoa('Hello, World!') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '');

    Note: btoa and atob only handle Latin-1 characters. For UTF-8 strings, encode first:

    const utf8ToBase64 = (str) => btoa(unescape(encodeURIComponent(str)));
    const base64ToUtf8 = (b64) => decodeURIComponent(escape(atob(b64)));
    

    Or use TextEncoder + manual base64 conversion for full Unicode support.

    Node.js

    // Standard Base64
    const encoded = Buffer.from('Hello, World!').toString('base64');

    // Base64URL (Node.js 16+) const urlSafe = Buffer.from('Hello, World!').toString('base64url');

    The base64url encoding was added in Node.js 16 and handles the alphabet swap and padding removal automatically.

    Python

    import base64

    Standard Base64

    encoded = base64.b64encode(b'Hello, World!').decode() # SGVsbG8sIFdvcmxkIQ==

    Base64URL

    url_safe = base64.urlsafe_b64encode(b'Hello, World!').decode() # SGVsbG8sIFdvcmxkIQ==

    Note that for this specific input, both produce the same output because there are no + or / characters. The difference only shows up when the encoded bytes happen to map to positions 62 or 63.

    Go

    import "encoding/base64"

    encoded := base64.StdEncoding.EncodeToString([]byte("Hello, World!")) urlSafe := base64.URLEncoding.EncodeToString([]byte("Hello, World!")) urlSafeNoPad := base64.RawURLEncoding.EncodeToString([]byte("Hello, World!"))

    Go has four encodings built in: StdEncoding, URLEncoding, RawStdEncoding (no padding), RawURLEncoding (no padding). Pick the one that matches your use case.


    Common pitfalls

    Pitfall 1: Mixing the alphabets

    If you encode with standard Base64 and decode with Base64URL (or vice versa), the result is wrong whenever the input contains +, /, -, or _.

    Fix: always know which variant produced the string. If unsure, try both:

    function flexibleDecode(b64) {
      try {
        return atob(b64);
      } catch {
        return atob(b64.replace(/-/g, '+').replace(/_/g, '/'));
      }
    }
    

    Pitfall 2: Forgetting UTF-8

    btoa('café') throws InvalidCharacterError in browsers because é is not Latin-1. Always encode UTF-8 first:

    btoa(new TextEncoder().encode('café').reduce((s, b) => s + String.fromCharCode(b), ''));
    

    Pitfall 3: Truncation

    Base64 is not a hash — it is fully reversible. But if you truncate the encoded string, you cannot decode it back. This sounds obvious, but it bites people who try to "shorten" a Base64 token by chopping the end.

    Pitfall 4: Comparing encoded strings for equality

    If you compare two Base64 strings for equality, make sure both use the same variant AND the same padding. SGVsbG8= and SGVsbG8 are the same bytes but different strings.

    Fix: decode both and compare the raw bytes, or normalize both to the same variant and padding before comparing.


    When NOT to use Base64 at all

    Base64 is a transport encoding, not a compression scheme. It makes data 33% larger. Do not use it when:

    • You could send the binary directly (modern HTTP handles binary fine).
    • You are trying to "obfuscate" data — Base64 is trivially reversible.
    • You are storing data in a database — store the raw bytes.
    Base64 is the right tool when:
    • You must put binary data into a text-only context (JSON, XML, email, URL).
    • You need a string representation that survives copy-paste and transport intact.

    Try it yourself

    Open the free RuMystic Base64 Encoder/Decoder:

    • Toggle between standard and URL-safe modes.
    • Handles UTF-8 correctly (no btoa Latin-1 limitation).
    • Drag in a file to encode or decode — file content never leaves your browser.
    • No signup, no rate limit, no upload to a server.
    Encode away.