Base64, URL and HTML Encoding Explained (and When to Use Each)
Three encodings that developers mix up constantly. What each one is for, how Base64 actually works bit by bit, and the classic bugs each one causes.
Base64, URL encoding and HTML entities all take text and turn it into other text, which is why developers confuse them. They solve three different problems, and using the wrong one is behind a surprising number of bugs, from broken links to cross-site scripting.
This guide explains what each encoding is for, how Base64 works at the bit level, and the mistakes I see most often. Try each one in the Base64, URL and HTML encoder as you read.
The short version
| Encoding | Problem it solves | Example |
|---|---|---|
| Base64 | Moving arbitrary bytes through text-only channels | Hi! becomes SGkh |
| URL (percent) encoding | Putting reserved characters inside a URL | a b&c becomes a%20b%26c |
| HTML entities | Putting text inside HTML without it becoming markup | <b> becomes <b> |
None of them is encryption. All three are fully reversible by anyone.
Base64: bytes as safe text
Early email systems were built for 7-bit ASCII text. Binary files such as images could not be sent as-is, because mail servers might strip the high bit or mangle line endings. The fix, standardized for email in MIME (RFC 2045) and later defined generally in RFC 4648, was to re-express bytes using only 64 characters that every system handles safely: A-Z, a-z, 0-9, + and /.
How it works
Base64 takes input three bytes (24 bits) at a time and splits them into four groups of six bits. Each 6-bit group, a number from 0 to 63, is looked up in the alphabet.
Encoding the text Hi!:
Characters: H i !
Bytes: 72 105 33
Binary: 01001000 01101001 00100001
6-bit groups: 010010 000110 100100 100001
Values: 18 6 36 33
Base64: S G k h
So Hi! becomes SGkh. When the input is not a multiple of three bytes, the output is padded with = so its length is a multiple of four. Hi encodes to SGk= and H to SA==.
Two practical consequences follow. First, Base64 output is about 33% larger than the input, since every 3 bytes become 4 characters. Second, you can tell roughly how long the original was from the encoded length.
Base64URL
Standard Base64 uses + and /, both of which have special meaning in URLs. Base64URL, also in RFC 4648, swaps them for - and _ and usually drops the padding. JSON Web Tokens use Base64URL, which is why you cannot always paste a JWT segment straight into a standard Base64 decoder without converting those characters first.
Where you meet Base64
Authorization: Basic dXNlcjpwYXNzheaders (that decodes touser:pass)data:image/png;base64,...URIs embedding images in CSS or HTML- Kubernetes Secrets, which are Base64-encoded, not encrypted
- Email attachments and PEM certificate files
- JWT headers and payloads (Base64URL)
The Kubernetes example is worth dwelling on. Seeing password: cGFzc3dvcmQ= in a manifest can create a false sense of security. Anyone with read access can decode it in one step.
A note on Unicode
JavaScript's built-in btoa() works on "binary strings" where each character is one byte. Characters outside Latin-1, such as emoji or most non-European scripts, need to be converted to UTF-8 bytes first, typically with TextEncoder. If an encoder fails on text with emoji, that is the reason.
URL encoding: reserved characters in links
URLs have structure. ? starts the query string, & separates parameters, = splits keys from values, # starts the fragment and / separates path segments. If a value contains any of those characters, it has to be escaped or it will change the meaning of the URL.
RFC 3986 defines percent encoding: a byte is written as % followed by two hex digits. A space becomes %20, & becomes %26, and the UTF-8 bytes of é become %C3%A9.
Consider a search for fish & chips:
Wrong: /search?q=fish & chips
Right: /search?q=fish%20%26%20chips
In the wrong version, the server sees a parameter q=fish and a second, nameless parameter chips.
encodeURIComponent vs encodeURI
JavaScript gives you two functions, and the difference matters:
encodeURIComponentescapes everything except letters, digits and- _ . ! ~ * ' ( ). Use it for a single value, such as one query parameter. This is what the RedBison URL tab uses.encodeURIleaves: / ? & = #alone so a full URL keeps working. Use it only on a complete URL that might contain spaces.
The classic bug is using encodeURIComponent on a whole URL, producing https%3A%2F%2F..., or using encodeURI on a parameter value and leaving its & unescaped. Better still, in modern code, build query strings with URLSearchParams, which handles encoding for you.
You will also see + used for spaces. That comes from the HTML form encoding (application/x-www-form-urlencoded), not from RFC 3986, which is why some decoders turn + into a space and others do not.
HTML entities: text that stays text
When you put text into an HTML page, the browser parses it as markup. If a user's display name is <script>alert(1)</script> and you insert it unescaped, the browser runs it. That is cross-site scripting (XSS), consistently near the top of the OWASP Top 10.
HTML encoding replaces the characters that can switch the parser out of text mode:
| Character | Entity |
|---|---|
& | & |
< | < |
> | > |
" | " |
' | ' |
Encoding & first matters, otherwise you double-encode the entities you just created.
In day-to-day work, your framework usually does this for you. React escapes text in JSX by default, as do most server-side template engines. The danger zones are the escape hatches: dangerouslySetInnerHTML, v-html, innerHTML, and building HTML strings by hand. The manual encoder is useful for preparing snippets for documentation, emails or CMS fields that will be inserted as raw HTML.
Context matters too. HTML encoding protects text inside elements and quoted attributes. It does not make a value safe inside a <script> block, a style attribute or an href that could start with javascript:. Those need context-specific handling.
Choosing the right one
Ask what will interpret the text next:
- A URL parser? Use URL encoding.
- An HTML parser? Use HTML encoding.
- A text-only channel that has to carry bytes? Use Base64.
And when the content is sensitive, none of these are protection. Use encryption.
Related tools
- JWT Decoder decodes Base64URL token segments into JSON
- Hash Generator for one-way fingerprints, which are different again
- URL Slug Generator when you want readable URLs rather than escaped ones