Is a Base64 URL Safe? Base64 vs. Base64URL Explained
Ordinary Base64 is not reliably safe to place in URLs. Learn when to use Base64URL, how padding and URL components affect it, and how to encode it in code.
Short answer: ordinary Base64 is not inherently URL-safe. Its alphabet includes + and /, which can have special meanings in URLs or be interpreted differently by form parsers. Base64URL, defined by RFC 4648, replaces those characters with - and _. Padding with = is a separate decision: keep or omit it according to the protocol that will read the value.
Even Base64URL is not permission to paste an arbitrary string into any URL component without considering that component’s syntax. For a query parameter, use a URL builder or percent-encode the value. For a path segment, use the rules of the system that consumes that path. And Base64 is encoding, not encryption: it does not make sensitive data secret.
1. What “URL-safe” means
Base64 represents groups of input bits using a text alphabet. Ordinary Base64 uses + and / for two of its symbols, and uses = as padding when the input length is not a multiple of three bytes. These characters can be awkward in URLs: slash is a generic URI delimiter, plus is a reserved character that some form-style query parsers interpret as a space, and equals commonly separates a query parameter name from its value.
Base64URL keeps the same encoding process but changes the two alphabet characters that cause the most trouble:
| Value | Ordinary Base64 | Base64URL |
|---|---|---|
| Alphabet symbol 62 | + |
- |
| Alphabet symbol 63 | / |
_ |
| Padding | = |
Usually =, unless the protocol allows omitting it |
RFC 4648 treats Base64URL as a distinct encoding and says it should not be referred to simply as “base64.” When interoperability matters, name the encoding explicitly and document the padding policy.
2. Padding and URL components
Should you remove the equals signs?
Not automatically. RFC 4648 says encoders include appropriate padding unless the referring specification explicitly says otherwise. A protocol can permit omitted padding when the encoded data length is known or can be inferred. If you strip padding but the consumer expects it, decoding may fail or behave differently.
Some protocols explicitly define their own accepted forms. For example, RFC 7235’s HTTP authentication token syntax accepts Base64URL with or without padding in that defined field and excludes whitespace. That is a protocol-specific rule, not a universal rule for every URL.
Does the location in the URL matter?
Yes. URI syntax distinguishes path, query, and fragment components. RFC 3986 lists hyphen and underscore as unreserved characters, while = and + are reserved sub-delimiters and / is a delimiter. Percent-encoding represents an octet when its character is outside the allowed set for a component or is being used as a delimiter there.
- Query parameter: pass the value through a query-string encoder or URL builder. Do not concatenate unescaped values by hand. In particular, some form parsers treat
+as a space. - Path segment: use Base64URL and follow the routing framework’s path-segment rules. Avoid assuming that a slash-containing Base64 string will remain one segment.
- Fragment: the fragment is interpreted by the client rather than sent as part of the HTTP request. Apply the consuming application’s rules.
Base64URL reduces the escaping problems associated with + and /, but padding and component rules still matter. Use percent-encoding when required by the URI component or API contract.
3. Encode and decode Base64URL in common languages
The examples encode the UTF-8 text hello world. For arbitrary binary input, read and write bytes rather than converting through text. The examples retain padding on encode where the runtime provides it; remove it only if the target protocol permits unpadded Base64URL.
JavaScript (Node.js and modern runtimes)
// Encode UTF-8 text as padded Base64URL
const input = "hello world";
const bytes = new TextEncoder().encode(input);
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
const base64url = btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_");
console.log(base64url); // aGVsbG8gd29ybGQ=
For Node.js specifically, its Buffer supports the Base64URL encoding label:
const encoded = Buffer.from("hello world", "utf8").toString("base64url");
console.log(encoded); // aGVsbG8gd29ybGQ (Node emits unpadded Base64URL)
const decoded = Buffer.from(encoded, "base64url").toString("utf8");
console.log(decoded); // hello world
Node’s documented Base64URL output omits padding. Confirm this behavior is accepted by your protocol before relying on it.
Python
import base64
raw = "hello world".encode("utf-8")
encoded = base64.urlsafe_b64encode(raw).decode("ascii")
print(encoded) # aGVsbG8gd29ybGQ=
# Decode padded Base64URL
decoded = base64.urlsafe_b64decode(encoded)
print(decoded.decode("utf-8")) # hello world
Python’s urlsafe_b64encode retains padding. If a specific protocol allows unpadded values, remove trailing = only for that protocol, and restore required padding before decoding if needed.
cURL and shell
On systems with the common GNU or BSD tools, encode bytes with base64, translate the alphabet, and remove line wrapping. Check your platform’s base64 options: GNU uses -w 0 to disable wrapping, while macOS/BSD commonly uses -b 0.
printf %s 'hello world' | base64 -w 0 | tr '+/' '-_'
On macOS/BSD, use this form if supported:
printf %s 'hello world' | base64 -b 0 | tr '+/' '-_'
These commands retain any = padding emitted by the encoder. To send the result as a query parameter without hand-building escaping, use a URL-aware client or percent-encode the parameter value. The commands above demonstrate encoding; they do not make a complete URL safe by themselves.
Build a query URL safely
In JavaScript, let URLSearchParams handle query serialization:
const token = "aGVsbG8gd29ybGQ=";
const url = new URL("https://example.com/verify");
url.searchParams.set("token", token);
console.log(url.toString());
In Python, use urllib.parse.urlencode rather than concatenating the query string:
from urllib.parse import urlencode
query = urlencode({"token": "aGVsbG8gd29ybGQ="})
url = "https://example.com/verify?" + query
print(url)
4. Validate and decode carefully
- Choose the alphabet deliberately. A generic Base64 encoder may produce ordinary Base64 rather than Base64URL.
- Confirm whether the receiving protocol expects padding, permits unpadded data, or requires another token format.
- Use a decoder for the selected alphabet. Do not assume every decoder accepts both alphabets or the same padding variants.
- Do not silently discard arbitrary characters or whitespace. RFC 4648 says decoders should reject characters outside the selected alphabet unless the referring specification says otherwise.
- If input arrives through a URL, decode the URL component according to its rules before applying the Base64URL decoder. Avoid decoding it twice.
- For security-sensitive tokens, validate the complete token structure and cryptographic checks; successful Base64 decoding alone proves nothing about authenticity.
5. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Decoded content has spaces or the token is corrupted | An ordinary Base64 + passed through a form-style query parser as a space |
Use Base64URL, or correctly percent-encode the query value with a URL builder. |
| A path route splits or returns 404 | Ordinary Base64 contains /, which the router treats as a path separator |
Use Base64URL for the path segment and follow the framework’s routing rules. |
| Decoder reports invalid length or padding | Padding was removed although the decoder or protocol expects it, or the input was truncated | Check the protocol’s padding rule and ensure the full value arrived. Do not guess by stripping characters. |
| Decoder rejects characters | Encoder and decoder use different alphabets, or the value has URL escaping still present | Match Base64 with Base64 or Base64URL with Base64URL; apply URL component decoding exactly once where appropriate. |
| Output contains line breaks | The command-line encoder wrapped long output | Disable wrapping using the option supported by your platform, or remove line wrapping deliberately before transmission. |
Different libraries produce strings with and without = |
They have different padding defaults | Specify and test the protocol’s padding policy at both ends. |
| Decoded value is readable, so it was assumed confidential | Base64 was mistaken for encryption | Use an appropriate cryptographic design for secrecy; Base64 provides representation only. |
6. Security, reliability, and size considerations
Base64 and Base64URL are encodings, not encryption or integrity checks. RFC 4648 explicitly warns that base encoding can visually hide information but provides no computational confidentiality. Do not put passwords, private keys, or secret token contents in a URL merely because they are encoded. URLs can be recorded in logs, browser history, analytics, and referrer data. Choose a suitable token design and transport for sensitive information.
Encoding also expands data: each group of up to three input bytes becomes four Base64 symbols, plus optional padding. Base64URL has the same expansion. Long encoded values can run into URL length limits imposed by clients, servers, proxies, or application frameworks. If a payload is large, send it in a request body or store it server-side and pass a short reference, according to the API design.
For reliable interoperability, add test vectors that cover input lengths with all three padding cases, values that exercise the +// substitutions, empty input if allowed, malformed characters, and round-trip decoding. Verify the exact serialized URL at the boundary where your HTTP client sends it.
7. Or skip the browser setup
If what you need is a screenshot of a URL while implementing a URL-processing flow, ScreenshotNeo provides a website screenshot API: one GET request returns an image or PDF. Here is the documented one-call pattern; see the ScreenshotNeo API docs for parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card.
8. FAQ
Is Base64URL the same as Base64?
No. It uses a different alphabet for the values represented by + and /. Name the format your protocol expects instead of treating the terms as interchangeable.
Can I use padded Base64URL in a URL?
It depends on the URL component and the receiving protocol. Padding is a separate issue from the Base64URL alphabet; follow the protocol and encode the URL component correctly.
Is Base64URL encryption?
No. It is reversible encoding and provides no confidentiality.
Can every Base64 decoder read Base64URL?
No. Use a decoder that explicitly supports the URL-safe alphabet and the padding form used by the producer.
Standards references
- RFC 4648: The Base16, Base32, and Base64 Data Encodings — alphabets, padding, decoder behavior, and security considerations.
- RFC 3986: Uniform Resource Identifier (URI): Generic Syntax — URI components, reserved characters, and percent-encoding.
- RFC 7235: Hypertext Transfer Protocol (HTTP/1.1): Authentication — an example of a protocol defining Base64URL token forms.


