HTML entities are how you write a character that would otherwise mean something to the parser. Escape a less-than sign and the browser prints it instead of looking for a tag; escape an ampersand and it stops trying to read the rest of the word as an entity name. This converts text to entities and back, in either direction.
Escape five characters, not everything
Only five characters can change how HTML is parsed, and in a UTF-8 page — which is every page written this century — escaping anything else is optional decoration.
- & becomes & — first, always. Escape it after the others and you will double-escape everything they produced.
- < becomes < and > becomes > — otherwise the browser starts reading a tag.
- " becomes " and ' becomes ' — these only matter inside an attribute value, but escaping them everywhere is simpler than deciding case by case.
- Everything else — accents, symbols, CJK, emoji — is ordinary text that a UTF-8 page renders correctly as-is.
When to escape everything anyway
The wider mode exists for the times when UTF-8 is not guaranteed end to end: a legacy CMS field, an email template passing through an old gateway, an XML document with an unclear declaration, a system that mangles anything above ASCII somewhere in the middle.
Escaping the whole non-ASCII range makes the text survive that, at the cost of being unreadable in the source. If you control the encoding, you do not need it — and if you find yourself reaching for it regularly, the real fix is upstream.
Named or numeric
A named reference such as © is readable; a numeric one such as © works for every character in Unicode. Named mode here uses a name where a common one exists and falls back to a numeric reference otherwise, so the output is always valid whichever you pick.
One caveat worth knowing: ' is defined in HTML5 and in XML, but not in HTML 4. If you are producing output for a genuinely old document type, use numeric mode, where the apostrophe comes out as ' and is understood everywhere.
Emoji, and why they usually break
An emoji is a single character but two UTF-16 code units, and most hand-written escaping loops iterate over units. The result is two references that decode back to a pair of unpaired halves rather than the original character — the reason a party popper turns into two question marks somewhere between a form and a database.
This converter iterates code points, so an emoji becomes one reference and comes back whole. A family emoji made of several people joined by zero-width joiners becomes one reference per component and still round-trips exactly.
Decoding safely
The usual way to decode entities in JavaScript is to assign the text to an element’s innerHTML and read it back. It works, and it is also how these converters become an XSS vector: one careless refactor later, the parsed markup is inserted into a page instead of read from it.
Nothing here does that. Decoding is an explicit scan for named, decimal and hexadecimal references, and the long tail of rarer names is resolved through an inert document that never runs a script. Numeric references in the 128–159 range are remapped the way browsers do it, so € comes out as a euro sign rather than an unassigned control character.
FAQ
Which characters actually have to be escaped in HTML?
In page text, only & and <. In practice you also escape > for symmetry, and " and ' because the same text often ends up inside an attribute where they would end the value early. That is the five-character minimal mode here, and it is the right default for almost every use.
Does escaping text make it safe against XSS?
Escaping those five characters is the correct defence for text placed into HTML content or an attribute value, but context decides. Text inserted into a script block, a style block, a URL or an event handler needs that context’s own escaping, and no amount of HTML escaping substitutes for it. Escape at the point of output, in the way that output requires.
What is the difference between ' and '?
They produce the same apostrophe, but ' is only defined in HTML5 and XML — HTML 4 never included it. Numeric mode emits ', which every parser has understood for as long as there have been parsers.
Why is my text full of &lt; after a round trip?
It was escaped twice. Each pass turns the & of an existing entity into &, so < becomes &lt;. Decode once for each time it was encoded — the swap button here makes that easy to check, since decoding once should give you back readable markup.
Is my text uploaded anywhere?
No. Both directions run entirely in your browser and nothing is transmitted, logged or stored. The page continues to work with the network disconnected, which is the simplest way to satisfy yourself that it is true.