Web Development
HTML Entity Encoding & Decoding: The Developer's Complete Guide
11 min read
A user pastes a code snippet into a comment box and suddenly the whole page layout breaks. The cause is not exotic — it is a single unescaped < character being parsed as the start of an HTML tag. The browser stops rendering the comment text and starts looking for element names, attributes and closing brackets. The fix is equally simple: replace that character with its HTML entity and the page renders correctly. This guide explains what HTML entities are, why every developer who handles user-generated content needs them, and how to avoid the common mistakes that cause real problems in production.
What HTML entities actually are
HTML reserves a small set of characters for its own syntax: angle brackets define tags, ampersands introduce entities, and quotes wrap attribute values. When you need to display one of these characters as literal text — not as markup — you replace it with an entity reference. An entity reference is either a short name wrapped in ampersand and semicolon (&lt;) or a numeric code (< or <). The browser interprets the entity during parsing and renders the corresponding character without treating it as structural HTML.
There are two families of entities. Named entities use human-readable names — &amp; for ampersand, &lt; for less-than, &gt; for greater-than. Numeric (decimal) entities use the character's Unicode code point: < for less-than, < in hexadecimal. Both approaches produce the same result in the browser; named entities are easier for humans to read, numeric entities cover every Unicode character.
The five entities that every web developer must know by heart are these: &amp; (&), &lt; (<), &gt; (>), &quot; ("), &apos; ('). The HTML5 spec also defines &nbsp; for a non-breaking space, which is the one named entity that does not correspond to a syntax character but still appears constantly in web content.
The five mandatory entities and their numeric alternatives
| Named Entity | Character | Decimal | Hex | When You Need It |
|---|---|---|---|---|
| &amp; | & | &38; | & | Every ampersand in text or attribute values — must be escaped first |
| &lt; | < | < | < | Any less-than sign outside a tag; displays code snippets, comparisons |
| &gt; | > | > | > | Greater-than sign in text; required alongside < in code blocks |
| &quot; | " | " | " | Double quotes inside HTML attribute values |
| &apos; | ' | ' | ' | Single quotes inside single-quoted attributes; XML compatibility |
Escape ampersands first — always
The ampersand is the gateway character for all entities. If you escape < first and leave the & unescaped, the browser may interpret your newly created &lt; as a literal ampersand followed by "lt;" rather than the entity &lt;. Always encode & as &amp; before touching anything else. This is the single most common encoding mistake in production code.
Why escaping user content is a security requirement
When an application stores text that a user typed and renders it back into HTML without encoding, any angle brackets in that text become part of the DOM. A malicious comment containing <script>alert('XSS')</script> is not displayed as text — it is executed as JavaScript. This is a cross-site scripting (XSS) attack, and it is one of the most common web vulnerabilities. The OWASP Top 10 has included XSS in every edition because developers still forget to escape user input.
The defense is straightforward: before any user-generated string enters HTML output, encode the five mandatory characters. When the string <script> goes through proper encoding, it becomes &lt;script&gt; and the browser renders it as harmless visible text. The same principle applies to attributes: if a user's display name contains a double quote and you insert it into a title attribute without encoding, the attacker can break out of the attribute and inject arbitrary HTML.
<!-- DANGEROUS: user input injected raw -->
<div class="comment">${userComment}</div>
<!-- SAFE: user input encoded before injection -->
<div class="comment">${encodeHtml(userComment)}</div>
<!-- DANGEROUS: name injected into attribute -->
<img src="photo.jpg" alt="${userName}">
<!-- SAFE: name encoded inside attribute -->
<img src="photo.jpg" alt="${encodeHtml(userName)}">HTML encoding vs URL encoding: they solve different problems
Developers frequently confuse HTML encoding with URL encoding (also called percent encoding). They serve completely different purposes. HTML encoding protects text content within an HTML document so that special characters are displayed, not parsed. URL encoding protects characters inside a URL path, query string or fragment so that the HTTP request is transmitted correctly.
A space in HTML becomes &nbsp; (or just a regular space if you do not need to prevent line breaking). The same space in a URL becomes %20. The ampersand in HTML becomes &amp; — but in a URL query string it is a separator, so if you need a literal ampersand in a parameter value it becomes %26. Using the wrong encoding in the wrong context breaks things: &nbsp; inside a URL will not work, and %20 inside HTML will appear as literal text.
The rule of thumb
Inside HTML text or attributes: use HTML entities. Inside URLs, file paths or query parameters: use percent encoding. When a value passes through both (for example, a search query in a link's href attribute), apply URL encoding first to the query value, then HTML-encode the entire href before inserting it into the markup.
Real-world scenarios where encoding matters
HTML encoding is not an academic exercise. Every application that displays user-generated content or renders dynamic data faces these situations daily:
- Code snippets in blog posts or documentation: A tutorial showing a Python comparison like
if x < 10 and y > 5:will break the surrounding HTML if the angle brackets are not encoded. The < starts a tag, the browser enters an error recovery mode, and the visible output is mangled. - User comments and forum posts: Any platform accepting free-text input — comments, reviews, chat messages — must encode output. The comment that simply types
a < bis not being malicious, but the unencoded < will still break rendering. - JSON inside script tags: Server-rendered JSON embedded in a
<script>block needs careful handling. Strings containing</script>will prematurely close the script tag. The safe pattern is to encode < and > within JSON string values, or use atype="application/json"data block with no JavaScript parsing. - Mailto links with special characters: A mailto link's subject line containing & must be encoded as
&amp;inside the HTML href, while the actual subject parameter uses percent encoding for spaces and non-ASCII characters. - Double ampersands in attributes: Writing
href="search?q=a&&b"directly in HTML is problematic. The browser sees the second & as the start of an entity reference. Both ampersands must be encoded:href="search?q=a&amp;b". - Less-than in data attributes: A data attribute like
data-config="{min: 0, max: 10}"is fine, butdata-template="<div>content</div>"must be fully encoded because the < and > will be interpreted as tags by the HTML parser before the JavaScript ever sees the attribute.
Over-escaping and under-escaping: the two failure modes
Getting encoding wrong in either direction creates visible bugs. Under-escaping means you did not encode enough characters. The consequences range from broken layouts (a stray < steals part of the page) to full XSS vulnerabilities. Under-escaping is the more dangerous failure because it affects security, not just display.
Over-escaping means you encoded characters that did not need encoding, or you encoded them multiple times. A user's name like Tom & Jerry displays as Tom &amp; Jerry if the ampersand gets double-encoded. This happens when a pipeline encodes the same string at two different stages — the application encodes once, then a template engine or content security layer encodes again. The result is unreadable text and broken display.
The way to avoid both problems is a clear rule: encode once, at the point of output, and never before. Store the raw value in your database. Apply HTML encoding at the exact moment you insert it into HTML markup. Do not encode when saving, do not encode when passing between backend functions, and do not encode when storing in a template variable. Single-point encoding prevents double-encoding; consistently encoding at output prevents under-encoding.
// WRONG: encode at storage time
db.save({ comment: encodeHtml(userInput) }); // double-encode risk
// RIGHT: encode at output time
const comment = db.getComment(id); // raw text
element.innerHTML = encodeHtml(comment); // encode once, hereWhich characters actually need encoding in each context
Not every character requires encoding in every situation. The minimum required set depends on where the text appears:
- In element content (between opening and closing tags): Encode
&and<. The>is technically optional but strongly recommended — browsers tolerate unencoded > in content, but it can cause parsing issues in edge cases withCDATA sections and adjacent text nodes. - In double-quoted attributes: Encode
&,<, and". The < is rarely encountered in attribute values but must be escaped when present. - In single-quoted attributes: Encode
&,<, and'instead of double quotes. - In JavaScript strings inserted into HTML: All five mandatory entities. When a JS variable flows into innerHTML, template literal, or document.write, every special character must be encoded.
- Inside CDATA sections (XML/XHTML): You can bypass entity encoding entirely inside
<![CDATA[...]]>blocks — except the sequence]]>which must never appear unescaped. CDATA is relevant for SVG and MathML embedded in HTML5 documents.
Browser quirks and edge cases
Modern browsers are forgiving about many HTML mistakes, but that forgiveness creates a false sense of security. Specific edge cases to be aware of:
- Unquoted attributes with special characters: Writing
class=foo&barwithout quotes will break because the browser sees&as an entity start. Always quote attribute values, and encode the content of those quotes. - The no-end-tag elements: Elements like
<script>,<style>, and<textarea>use a special parsing mode. Inside these elements, almost no entity decoding occurs in HTML5 — you must encode<and&to prevent premature tag closing, but named entities like&nbsp;are treated as literal text. - Invalid entity references: The browser silently drops semicolons from unrecognized entities. Typing
&foo;renders as the literal text&foo;in most browsers, but&foo(no semicolon) can consume the next character — a bug that is extremely hard to trace. - Numeric character references vs control characters: Using numeric entities for control characters like
�(null) is forbidden in HTML5. The parser silently replaces them with U+FFFD (replacement character) or drops them entirely.
The trailing-semicolon trap
An entity reference without a trailing semicolon is ambiguous. &lt without the semicolon is parsed as the entity &lt followed by whatever character comes next. A user typing &cat accidentally creates an entity &ca followed by a stray t. Always verify that your encoding function includes the semicolon for every entity it produces.
Encoding strategies in practice
Most web frameworks include built-in encoding functions. React's JSX auto-encodes expression values inside element content (but dangerouslySetInnerHTML bypasses this entirely — use it only with trusted, pre-sanitized content). Server-side templating engines like Jinja2, Handlebars and Blade all provide escape filters. The key point: know where your framework encodes automatically and where it does not.
For the five mandatory entities, a minimal encoding function is straightforward. The function replaces each special character with its named entity, ampersands first:
function encodeHtml(str) {
return str
.replace(/&/g, "&") // must be first
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// Usage:
const safe = encodeHtml('He said "a < b & c > d"');
// → He said "a < b & c > d"Prefer the standard library
The manual function above is for understanding, not production use. In Node.js use the built-in text Encoding API or a well-tested library. In the browser, textContent assignment handles most cases automatically. The DOMParser API is the safest route for user-supplied HTML when you need to allow some tags but block others — it parses without executing scripts.
HTML encoding tools on Convrs
When you need to quickly encode or decode HTML entities — whether you are debugging a rendering issue, preparing content for a CMS, or testing how escaped text will look — the HTML Encode/Decode tool on Convrs handles both directions in the browser. Paste your text, choose encode or decode, and get the result instantly. No file upload, no server round-trip, no data leaving your device. It covers all five mandatory entities plus numeric references, so you can verify that your output encoding matches what the browser expects.