Developer
Base64 vs URL Encoding vs HTML Encoding — When to Use Each (with Examples)
Base64 vs URL encoding vs HTML encoding — what each one does, why they exist, when to use which, and the security gotchas (XSS, double-encoding) developers miss.
"Encoding" is one of those words developers use to mean three different things, all of them important and easy to confuse. Base64, URL encoding, and HTML encoding solve different problems — using the wrong one is a common source of bugs and security holes.
This guide explains what each encoding actually does, when to use which, and the security traps to avoid.
Quick Answer
| Use case | Use | Why |
|---|---|---|
| Embedding binary data (image, file) in JSON or HTML | Base64 | Survives text-only transports |
| Putting data in a URL query string or path | URL encoding | Reserved characters (?, &, /, ) need escaping |
| Inserting user input into HTML | HTML encoding | Prevents XSS by escaping <, >, &, ", ' |
| Storing a password securely | None of these — use bcrypt | These are encodings, not encryption |
| Hiding data from a casual viewer | Don't. | All three are trivially decoded |
What Each Encoding Actually Does
Base64 — text-safe binary
Base64 converts binary data (bytes 0-255) into a text string using only 64 safe characters: A-Z, a-z, 0-9, +, /, with = for padding.
"Hello, World!" → "SGVsbG8sIFdvcmxkIQ=="
Every 3 bytes of input become 4 characters of output, so Base64 makes data ~33% larger.
Why it exists: Some channels (email bodies, JSON strings, URLs) can only carry text. If you need to send a PNG image inside a JSON field, you can't put raw bytes there — they'd corrupt the JSON. So you Base64-encode the image into text, send the text, and decode on the other side.
URL encoding (a.k.a. percent encoding)
URL encoding replaces unsafe characters in a URL with % followed by the hex code of the byte.
"hello world" → "hello%20world"
"price=$10" → "price%3D%2410"
"naïve" → "na%C3%AFve"
URLs have reserved characters (?, &, =, /, #, , +, etc.) that mean specific things. If your data contains any of these, you have to escape them, or the URL parser will misinterpret your data.
Why it exists: A URL like https://example.com/search?q=cats&dogs is ambiguous — is your search "cats&dogs" or are you trying to pass two parameters? URL encoding removes the ambiguity: q=cats%26dogs means "cats AND dogs as a single value."
HTML encoding (entity encoding)
HTML encoding replaces characters that have special meaning in HTML with named or numeric entities.
"<script>" → "<script>"
"O'Reilly" → "O'Reilly"
"5 & 10" → "5 & 10"
"©" → "©" or "©"
Why it exists: If you take user input and drop it directly into an HTML page without escaping, an attacker can inject <script>alert(1)</script> and run JavaScript in your users' browsers. This is called XSS (Cross-Site Scripting) and is one of the most common web vulnerabilities. HTML encoding prevents it by making angle brackets and quotes display as text instead of being parsed as HTML.
The Most Common Mistakes
Mistake 1: Using Base64 as "encryption"
Base64 is not encryption. It's a reversible encoding. Anyone with the encoded string can decode it back in one second:
import base64
base64.b64decode("UGFzc3dvcmQxMjMh").decode()
# "Password123!"
If you store passwords as Base64 in your database, you have plaintext passwords. Use bcrypt or Argon2 (see MD5 vs SHA-256 vs bcrypt).
Mistake 2: URL-encoding then putting in HTML, or vice versa
These encodings stack incorrectly. If you take user input, URL-encode it, and drop it into HTML, you've URL-encoded into HTML — but you haven't HTML-encoded it. The HTML < is still a <.
Rule of thumb: encode for the context where the data ends up. Going into a URL? URL-encode. Going into HTML? HTML-encode. Going into both (e.g., a URL inside an HTML attribute)? Apply both, outermost first.
Mistake 3: Double-encoding
If you URL-encode a string that's already URL-encoded, you get nonsense. hello%20world becomes hello%2520world (the % becomes %25). Triple-encoding is a real bug developers hit when middleware (browser, proxy, framework) encodes data they were about to encode themselves.
Mistake 4: Using Base64 in a URL without converting to base64url
Standard Base64 uses + and /, which are reserved in URLs (+ means space in query strings, / is a path separator). For URL-safe Base64, use base64url which substitutes - for + and _ for /. JWTs use base64url for this reason.
Mistake 5: Trusting the client to encode safely
Encoding for security (escaping HTML, escaping SQL, etc.) must happen on the server, not in client-side JavaScript. An attacker can simply send raw, un-encoded data to your server. Always escape on the server side, just before the data hits its final destination (HTML, SQL, shell command).
When to Use Which — Real Scenarios
Sending an image in a JSON API response
→ Base64. JSON can only carry text. Convert the image bytes to Base64, put them in a string field. Send a data:image/png;base64,iVBORw0K... data URI or a plain Base64 blob.
Passing a search query in a URL
→ URL encoding. The browser already encodes form data automatically (q=hello+world), but if you build URLs by hand, use encodeURIComponent() in JavaScript or urllib.parse.quote() in Python.
Displaying a user's profile bio that contains HTML characters
→ HTML encoding. Otherwise an attacker who writes <script>... in their bio runs that script in every visitor's browser. Every web framework has a helper: htmlspecialchars() in PHP, escape() in Jinja, automatic in React when you use {userBio} as text.
Storing a binary attachment in a database column
→ Either is fine, but prefer a BLOB/BYTEA column. If you have to use a text column, Base64. Storing raw bytes in a text column corrupts data because of character-encoding conversions.
Writing a JWT (JSON Web Token)
→ base64url, not standard Base64. JWTs are passed in URLs (?token=...) and Authorization headers, both of which dislike + and /.
Sending a binary file over email
→ Base64. Email bodies are 7-bit ASCII by spec. The MIME Content-Transfer-Encoding: base64 header tells the receiver to decode.
Building a CSV cell that contains a comma
→ None of these. CSV uses double-quote escaping: wrap the cell in quotes, double up any internal quotes. Hello, "World" becomes "Hello, ""World""".
Encoding in Code
Base64
# Python
import base64
encoded = base64.b64encode(b"Hello, World!").decode()
# "SGVsbG8sIFdvcmxkIQ=="
decoded = base64.b64decode(encoded).decode()
# "Hello, World!"
# base64url (for JWTs, URLs)
base64.urlsafe_b64encode(b"Hello").decode().rstrip("=")
// Browser / Node.js
btoa("Hello, World!"); // "SGVsbG8sIFdvcmxkIQ=="
atob("SGVsbG8sIFdvcmxkIQ==") // "Hello, World!"
// base64url
btoa("Hello").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
URL encoding
import urllib.parse
urllib.parse.quote("hello world")
# "hello%20world"
urllib.parse.quote_plus("hello world") # uses + for space (form encoding)
# "hello+world"
urllib.parse.unquote("hello%20world")
# "hello world"
encodeURIComponent("hello world & dogs");
// "hello%20world%20%26%20dogs"
decodeURIComponent("hello%20world%20%26%20dogs");
// "hello world & dogs"
⚠️ Use encodeURIComponent for query string values, not encodeURI (which leaves &, =, ? alone — wrong for parameter values).
HTML encoding
import html
html.escape("<script>alert(1)</script>")
# "<script>alert(1)</script>"
// No built-in; common pattern:
function escapeHtml(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Or use a framework — React, Vue, and Angular escape automatically when you interpolate text.
Frequently Asked Questions
Is Base64 secure?
No. Base64 is an encoding, not encryption. Anyone can decode Base64 in seconds with a free online tool. Treat Base64-encoded data as if it were plain text.
Why does Base64 add = at the end sometimes?
Base64 encodes 3 bytes at a time into 4 characters. If your input length isn't a multiple of 3, the encoder pads it with = to keep the output length a multiple of 4. = is the padding character: one = means the input had 2 bytes in the final group; two = means 1 byte. The padding is decorative — most decoders accept Base64 without padding too.
What is the difference between base64 and base64url?
Standard Base64 uses + and /. base64url substitutes - and _ so the string is safe to use in URLs and filenames without further encoding. JWTs and many web APIs use base64url.
Why do URLs sometimes have + and sometimes %20 for spaces?
Both encode a space, but in different contexts:
%20is the official URL encoding for a space, valid anywhere in a URL.+is a legacy alternative for spaces in the query string part of a URL (after the?), defined by theapplication/x-www-form-urlencodedspec used by HTML forms.
Modern best practice: use %20 everywhere. But you'll still see + in legacy form data.
Does Base64 encrypt my data?
No. Encryption requires a key. Base64 is reversible by anyone. If you want to protect data, use real encryption (AES, NaCl, libsodium) or a hash (bcrypt, Argon2) for passwords.
Why is my data showing weird characters after encoding?
Probably a character-set mismatch. UTF-8 byte sequences for non-ASCII characters look like garbage if you decode them as Latin-1. Always use the same character encoding (almost always UTF-8) for both sides. If you see é instead of é, that's UTF-8 being decoded as Latin-1.
What is the difference between encodeURI and encodeURIComponent?
encodeURI is for encoding a complete URL — it leaves :, /, ?, #, & alone. encodeURIComponent is for encoding a piece of data going into a URL parameter — it encodes everything that's not a letter, digit, or a few unreserved characters. Use encodeURIComponent for query values; use encodeURI rarely.
How do I prevent XSS in my web app?
Three layers:
- HTML-encode user input before inserting it into HTML — every modern framework does this automatically if you use the text interpolation syntax.
- Sanitize HTML (e.g., DOMPurify) only when you must allow some HTML — never trust a regex for this.
- Set a Content-Security-Policy header to limit what scripts the browser will execute even if injection happens.
What encoding does a JWT use?
JWTs are three base64url-encoded segments joined by dots: header.payload.signature. Each segment is base64url (URL-safe Base64 without padding). Inside each segment is JSON.
Try It Yourself
All three encoders run in your browser — nothing leaves your machine:
- Base64 encoder / decoder
- URL encoder / decoder
- HTML entity encoder
- Hex encoder — for when you need raw byte representation
- JWT decoder — decode a token and see its base64url segments
Want to learn more?
Explore free chapter-wise notes with quizzes and code playground
Prefer watching over reading?
Subscribe for free.