developer tools

Base64, URL Encoding and HTML Entities: A Complete Developer Reference

Deep reference guide to Base64, URL percent-encoding, and HTML entity encoding: when to use each, how to implement correctly, and common pitfalls.

ZakGT Tools·11 min read

The Three Encodings Every Developer Confuses

Ask ten developers to explain the difference between Base64, URL encoding, and HTML entity encoding, and you will get ten different answers — many of them wrong. These three encoding schemes are often conflated because they all transform data into a different textual representation, but they serve entirely distinct purposes, operate at different layers of the stack, and must never be substituted for one another.

**Base64** solves the problem of transmitting binary data over channels that only support ASCII text. It was designed for email (MIME), but today it appears everywhere binary needs to travel through text: JWT tokens, data URIs, API payloads embedding images, and SSH key files.

**URL encoding** (formally called percent-encoding, defined in RFC 3986) solves the problem of including arbitrary characters in a URL without breaking the URL's structure. Characters like `&`, `=`, `#`, and spaces have reserved meanings in URLs, so any data that might contain them must be encoded before inclusion.

**HTML entity encoding** solves the problem of including special characters inside HTML documents without the browser interpreting them as markup. `<`, `>`, `&`, and `"` are the characters that matter most, because an unencoded `<` in user-supplied content can open an HTML tag and introduce a cross-site scripting (XSS) vulnerability.

Confusing these three causes real bugs: double-encoded URLs that return 404 errors, base64 strings injected into HTML that break the page structure, and — most dangerously — missing HTML entity encoding that leaves XSS vulnerabilities in production. This reference covers all three with precision.

Base64 Encoding: Mechanics, Variants, and Real-World Usage

Base64 works by taking three bytes of binary input (24 bits) and encoding them as four ASCII characters chosen from a 64-character alphabet (`A-Z`, `a-z`, `0-9`, `+`, `/`). The output is padded with `=` characters to make the length a multiple of four. This scheme means every 3 bytes of input becomes 4 bytes of output — a **33.3% size increase**. For large binary files, this is significant overhead.

The canonical Base64 alphabet uses `+` and `/` for the 62nd and 63rd characters. This is a problem in URLs because `+` means space and `/` is a path separator. For URL-safe contexts, **Base64url** (RFC 4648 §5) substitutes `-` for `+` and `_` for `/`, and omits padding. JWT tokens use Base64url for their header and payload segments.

**Encoding in practice:**

Node.js: ```javascript // Encode const encoded = Buffer.from('Hello World').toString('base64'); // → 'SGVsbG8gV29ybGQ='

// Decode const decoded = Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString('utf8'); // → 'Hello World' ```

Python: ```python import base64 encoded = base64.b64encode(b'Hello World').decode('utf-8') # → 'SGVsbG8gV29ybGQ=' decoded = base64.b64decode('SGVsbG8gV29ybGQ=').decode('utf-8') ```

**Common use cases in 2026:** embedding images in CSS as data URIs (`background: url('data:image/png;base64,iVBOR...')`), encoding binary file attachments in JSON API payloads (when multipart upload is not feasible), storing encryption keys and certificates in environment variables, and representing cryptographic signatures in JWT tokens.

**Critical reminder:** Base64 is encoding, not encryption. A Base64-encoded string is trivially decodable by anyone. Never use it to obscure sensitive data.

URL Percent-Encoding: RFC 3986 Explained

RFC 3986 defines which characters are allowed in a URL without encoding. Characters are classified as **unreserved** (letters, digits, `-`, `.`, `_`, `~`) which can appear anywhere without encoding, and **reserved** which have syntactic meaning and must be percent-encoded when used as data.

Percent-encoding replaces a character with `%` followed by the two-digit hexadecimal value of the byte. Space becomes `%20`, `&` becomes `%26`, `=` becomes `%3D`, `#` becomes `%23`.

**Query strings vs. path segments:** encoding rules differ slightly. In a query string, the `+` character is traditionally treated as a space (application/x-www-form-urlencoded encoding, used in HTML forms). This is NOT part of RFC 3986 — it is a legacy convention. To avoid ambiguity, use `%20` for spaces in all contexts.

**Double-encoding is the most common bug.** If you receive a URL-encoded string and encode it again, the `%` becomes `%25`, turning `%20` into `%2520`. When the server decodes it, it gets the literal string `%20` instead of a space. Always decode before processing and re-encode before embedding.

Node.js provides two functions: - `encodeURIComponent()` — encodes everything except unreserved characters. Use this for query string values and path segment values. - `encodeURI()` — preserves reserved characters (`/`, `?`, `&`, `=`, `#`, etc.). Use this for whole URLs.

```javascript const value = 'search term & filter=active'; const url = `https://api.example.com/search?q=${encodeURIComponent(value)}`; // → https://api.example.com/search?q=search%20term%20%26%20filter%3Dactive ```

Python's `urllib.parse.quote()` (for path segments) and `urllib.parse.urlencode()` (for query dicts) follow the same pattern. Always use the appropriate function for the context — never concatenate raw user input into a URL.

HTML Entity Encoding: The XSS Defense Layer

HTML entity encoding converts characters that have special meaning in HTML into their entity equivalents, so the browser renders them as text rather than interpreting them as markup. The five characters that must always be encoded when outputting user-supplied content in HTML are:

| Character | Entity | Hex Entity | Description | |-----------|--------|------------|-------------| | `<` | `&lt;` | `&#x3C;` | Less-than / tag open | | `>` | `&gt;` | `&#x3E;` | Greater-than / tag close | | `&` | `&amp;` | `&#x26;` | Ampersand / entity start | | `"` | `&quot;` | `&#x22;` | Double quote / attribute delimiter | | `'` | `&#x27;` | `&#x27;` | Single quote / attribute delimiter |

Failing to encode `<` and `>` allows an attacker to inject `<script>` tags. Failing to encode `"` inside an HTML attribute allows attribute injection. The `&` must be encoded to prevent partial entity conflicts.

**In React and Vue**, HTML entity encoding is automatic — the framework escapes all interpolated values by default. Vulnerabilities arise only when developers explicitly opt out using `dangerouslySetInnerHTML` (React) or `v-html` (Vue). Only use these directives with content that has been sanitized by a library like DOMPurify.

**In server-side templates**, the escaping behavior depends on the template engine: - Jinja2 (Python): `{{ value }}` auto-escapes. `{{ value | safe }}` disables escaping. - Handlebars (JS): `{{ value }}` auto-escapes. `{{{ value }}}` renders raw HTML. - PHP: `htmlspecialchars($value, ENT_QUOTES, 'UTF-8')` is the manual method for echoing user data.

The OWASP XSS Prevention Cheat Sheet recommends encoding HTML entities **at output time**, not at input time. This preserves the original data and allows it to be safely displayed in different output contexts (HTML, JSON, plain text) with context-appropriate encoding.

When Encodings Stack: JWT, Data URIs, and Multi-Layer Systems

Real-world systems frequently chain multiple encoding layers. Understanding the correct order and the boundaries between layers is essential for building correct integrations.

**JWT tokens** are a canonical example: a JWT is three Base64url-encoded segments joined by `.` (dots). The header and payload are JSON objects, Base64url-encoded. The signature is a raw binary HMAC or RSA signature, also Base64url-encoded. When a JWT is transmitted in an HTTP `Authorization` header, no additional encoding is needed. When embedded in a URL query parameter, the dots are safe (they are unreserved characters), but the padding `=` characters in standard Base64 are not — another reason JWT uses Base64url with padding omitted.

**Data URIs in CSS:** `url('data:image/png;base64,iVBOR...')`. Here, binary image data is Base64-encoded and then embedded in a CSS string inside a URL function. If the CSS is then embedded in an HTML `<style>` attribute, you have three layers: binary → Base64 → CSS → HTML. Each layer must be correctly encoded; an unescaped `"` in the CSS would break the HTML attribute.

**OAuth redirect URIs:** when a redirect URI is embedded as a query parameter in an authorization URL, it must be URL-encoded. If the redirect URI itself contains query parameters, those inner parameters must be encoded first, then the entire redirect URI string is encoded again. The result looks like double-encoding but is correct: `redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback%3Fsource%3Doauth`.

The general rule for stacked encodings: **encode from the inside out**. Encode the innermost data first, then embed it in the next layer and encode that. Decode from the outside in.

Security Implications: Encoding as Attack Surface

Encoding is not just a data transformation concern — it is a significant security surface. Attackers actively exploit encoding to bypass input validation and inject malicious payloads.

**Encoding-based XSS bypasses:** if a server validates input for `<script>` but not for `&#60;script&#62;` (the HTML entity form), an attacker can submit the entity-encoded version. The validator passes it, and the browser decodes it at render time, executing the script. Robust XSS prevention requires sanitization *after* decoding, not before.

**Double URL-encoding attacks:** a firewall rule blocking requests containing `../` (path traversal) might not block `%2E%2E%2F`. If the server double-decodes the URL (first by the web framework, then again by a file access routine), the traversal succeeds. This is why decoding must happen exactly once, at a consistent layer.

**Base64 confusion:** some developers incorrectly assume Base64-encoded data is safe to pass to database queries or shell commands without further sanitization. It is not. `'; DROP TABLE users; --` Base64-encodes to a perfectly valid Base64 string that, once decoded and used unsafely, causes SQL injection. Encoding transforms the *representation* of data; it does not make the *content* safe.

**Canonical form attacks:** Unicode allows the same visible character to be represented in multiple byte sequences (composed vs. decomposed forms). An attacker can use a Unicode lookalike for `<` that bypasses string matching but renders identically in a browser. Always normalize Unicode input to a canonical form (NFC or NFKC) before validation and encoding.

The defense is layered: validate the semantic meaning of input, normalize Unicode, encode for the specific output context, and never trust any encoding scheme as a security control in isolation.

Quick Reference: Encoding Cheat Sheet for Daily Development

Bookmark this section. It covers the most common encoding decisions developers face daily in 2026.

**When to use Base64:** - Embedding binary data (images, files, keys) in JSON or XML payloads - Storing binary secrets in environment variables or config files - Email attachments and MIME encoding - JWT header/payload (use Base64url variant) - Data URIs in HTML/CSS (`data:image/png;base64,...`)

**When to use URL encoding:** - Any user-supplied value appended to a URL query string - Special characters in URL path segments - Form submission data (HTML forms use `application/x-www-form-urlencoded`) - OAuth parameters, redirect URIs embedded in other URLs

**When to use HTML entity encoding:** - Outputting user-supplied text inside HTML element content - User data inside HTML attribute values - Dynamic content in server-rendered HTML templates - Any value that might contain `<`, `>`, `&`, `"`, or `'`

**Platform quick reference:**

| Language | Base64 | URL Encode | HTML Escape | |----------|--------|------------|-------------| | Node.js | `Buffer.from(s).toString('base64')` | `encodeURIComponent(s)` | Use `he` library or React | | Python | `base64.b64encode(b)` | `urllib.parse.quote(s)` | `html.escape(s)` | | PHP | `base64_encode($s)` | `urlencode($s)` | `htmlspecialchars($s, ENT_QUOTES)` | | Go | `base64.StdEncoding.EncodeToString(b)` | `url.QueryEscape(s)` | `html.EscapeString(s)` | | Java | `Base64.getEncoder().encodeToString(b)` | `URLEncoder.encode(s, UTF_8)` | Manual or Apache Commons |

For testing and one-off conversions, browser-based tools like the Base64 encoder, URL encoder, and HTML entity encoder on this site handle all three encoding types client-side with no server upload required.

← Back to ArticlesTry the Free Tools

More in developer tools

View all developer tools guides →