tooldura

Developer Tools

HTML Entities: Five Characters, and Why Escaping Depends on Context

T
tooldura editorial
8 min readUpdated August 14, 2026Open tool →

Entities exist because HTML has to distinguish markup from text using characters that also appear in ordinary writing. Once a document can say < to open a tag, it needs another way to say a literal less-than sign. That is the whole idea. Everything complicated about entities comes from the second question: which characters need it, and where.

The Five That Matter

In the body of a document, exactly two characters can change how the parser reads what follows. A < opens a tag. An & opens a character reference. Everything else is text.

Inside an attribute value the list grows by the quote mark that delimits it, so " in a double-quoted attribute and ' in a single-quoted one. The greater-than sign is the fifth by convention rather than necessity: a bare > in text is harmless in HTML5, and the spec says so, but escaping it costs nothing and protects you from the one real case, an unquoted attribute value.

That is the complete list for HTML: & < > " and '. Every other entity you have seen, from &nbsp; to &mdash; to &hearts;, is a convenience for typing or transport, not a correctness requirement.

Escaping Is Per Context, Not Per Document

The same string needs different treatment depending on where it lands. This is the part that HTML escaping alone does not solve.

Where the text landsWhat it needsWhat goes wrong otherwise
Element bodyHTML escapingA < opens a tag and the rest of the page shifts
Quoted attributeHTML escaping, quotes includedA matching quote closes the attribute early
Unquoted attributeQuote the attribute firstA space ends the value and the rest becomes new attributes
Inside a script blockJavaScript string escapingEntities are not decoded there; a quote breaks the script
URL or query parameterPercent-encodingAn ampersand starts a new parameter
CSS valueCSS escapingA closing brace escapes the rule and reaches the page

The Case Where Entities Do Nothing

A script block is parsed as raw text, not as HTML. Character references are not decoded inside it. Writing &amp; in JavaScript gives you those five literal characters, not an ampersand, and escaping user data as HTML before dropping it into a script does not make it safe. It only makes it wrong.

There is a related trap that catches people who do escape correctly: the sequence </script> inside a JavaScript string ends the block, wherever it appears. A string containing the literal text "</script>" closes the element and everything after it is parsed as HTML. The fix is to break the sequence, usually by writing <\/script>, and it is why JSON embedded in a page has to be escaped for the HTML parser as well as being valid JSON.

The general rule underneath both: you escape for the parser that reads the text next, in the order it reads it. HTML first, then whatever the content becomes.

Four Things Worth Knowing

None of these are edge cases. They come up in ordinary work, usually as a bug someone spends an afternoon on.

1

Double encoding has a signature

Text that reads &amp;lt; was escaped twice: an already-escaped string went through the escaper again. Its ampersand became &amp; and the rest stayed. The visible symptom is entities on the page instead of characters, and the fix is one more round of decoding, not another escaper.

2

&apos; is younger than you think

It comes from XML and became valid HTML only in HTML5. In HTML 4 it is not defined, so old parsers print it literally. That is why every serious templating engine emits &#39; for an apostrophe and not the readable name.

3

The semicolon is not always required

Browsers still decode a legacy set without it, so &copy 2026 renders a copyright sign. This is why &notit; famously decodes as ¬it; and why anything parsing HTML has to handle the case, however much the spec discourages writing it.

4

Numeric references cover all of Unicode

HTML 4 named 252 characters. HTML5 names 2,231, but neither covers emoji or most scripts, so &#128512; is the only way to write one as a reference. Numbers work for every character that exists; names only for the ones somebody wrote down.

🔤

If the page is UTF-8, most entities are optional

Entities for accented letters and symbols solved a transport problem from the days of ASCII-only pipelines and mislabelled charsets. A document served as UTF-8 can contain café, — and 😀 directly, and doing so is smaller, more readable, and easier to search in your own source. Escaping beyond the five is worth doing when the output has to survive a channel you do not control, and not much else.

Escape or decode any text

Named, decimal or hex, with a table of every character it touched.

Open HTML Entity Encoder →

The Entities That Hide

Some references stand for characters you cannot see, and they cause a specific kind of bug: text that behaves oddly with nothing visibly wrong.

&nbsp; is a no-break space, U+00A0. It looks exactly like a space and never breaks a line, so a heading full of them refuses to wrap and overflows on a narrow screen. It also does not match a plain space in a search or a comparison, which is why text pasted from a word processor sometimes fails a lookup that should obviously succeed.

&shy; is a soft hyphen, invisible until the word breaks, at which point a hyphen appears. Useful in long compound words, mystifying when it lands in the middle of a slug or an identifier.

&zwnj; and &zwj; control whether adjacent characters join, which matters in Arabic and Indic scripts and in emoji sequences: a zero-width joiner is what turns separate emoji into a family. Any tool that lists what an entity resolves to should name these rather than print them, since printing them shows nothing at all.

Escaping Is Not Sanitising

The two get used interchangeably and are not the same operation.

Escaping converts characters so a parser treats them as data. It is lossless and reversible: the original text is recoverable, and nothing is judged or removed. Sanitising parses untrusted markup and removes what is dangerous, keeping the rest as markup. It is lossy and irreversible by design.

The choice is about intent. If the user's input is text and should stay text, escape it. If it is meant to be markup, a comment with formatting or a rich text field, sanitise it with a real library and keep the tags you allow.

The expensive mistake is escaping at the wrong time: escaping input on the way into the database rather than on the way out to a page. Store what the user actually typed, and escape at the moment you render, for the context you are rendering into. Escaping on input leaves you with a database of &amp;quot; and no way to know which layer added it.

Frequently Asked Questions

Related Tools

Keep Reading