HTML Entity Encoder / Decoder
Escape text for HTML, decode entities back, and handle the quirks browsers add
What was escaped
Nothing yet.
The ones worth knowing
&&Always. An unescaped & is the most common malformed HTML there is<<Always, in text content>>Only strictly needed after ]], but conventional""Inside a double-quoted attribute''Inside a single-quoted attribute. Not in HTML 4, so ' is safer A space that does not wrap or collapse. Invisible, and not an ordinary space––Ranges: 9–5——A break in a sentence……One character, not three full stops©©®®™™××Multiplication, not the letter x−−A real minus sign, wider than a hyphen°°££€€’’The correct apostrophe in prose““””­A soft hyphen: only appears if the line breaks there‍A zero-width joiner. What holds a multi-part emoji togetherOnly five characters actually mean anything to an HTML parser. Escape more than those five and you are making a decision about whatever will read the output next, not satisfying the format.
& & always, everywhere
< < in text content
> > conventional, and only strictly needed after ]]
" " inside a double-quoted attribute
' ' inside a single-quoted attribute
Everything else is a choice about the system reading it,
not a requirement of HTML.The ampersand is the one that bites
<a href="?a=1&b=2"> what people write
<a href="?a=1&b=2"> what it has to be
A bare & starts a character reference. &b is not one, so a
browser recovers and leaves it alone, and everything works
until somebody adds a parameter called "copy" or "reg".A bare & begins a character reference. When what follows is not a reference the browser recovers and leaves it alone. So unescaped ampersands look like they work, right up until a query parameter is called copy, reg, times orlang, at which point ©=1 becomes©=1 and the link stops working with nothing to say why.
Validators have complained about this since 1997 and it is still the most common malformed HTML there is.
Attributes are a different context
<img alt="Tom & Jerry's"> fine
<img alt='Tom & Jerry's'> fine
<img alt=Tom's> a bomb waiting to go off
An unquoted attribute ends at the first space, so anything
interpolated into one can add attributes of its own.Inside a double-quoted attribute, a double quote ends the attribute. Inside a single-quoted one, a single quote does. So the escaping that is correct in a text node is not sufficient in an attribute, and this is the gap most hand-rolled escaping functions fall into.
' was not in HTML 4. It arrived with XHTML and then HTML5, so ' is the safer bet and it is what this produces. The switch above adds both quote characters to whatever else is being escaped.
Named or numeric
A named reference is readable and a numeric one always works. HTML5 defines 2,231 names; XML defines five. So an XML or SVG document that uses is malformed, because XML has never heard of it, and  in the same place is fine.
Which is the rule worth carrying: named references for HTML, numeric for everything else. Anything that will pass through XML, an RSS feed, a SOAP envelope or an older parser wants numbers.
What browsers accept that nothing else does
€ -> € not U+0080
’ -> ’ not U+0092
— -> — not U+0097
Those code points are C1 control characters, which nobody
ever means. The number was written by something assuming
Windows-1252, and the HTML standard requires browsers to
read it that way.Two more, both handled here because refusing them would make a decoder that disagrees with every browser:
- A missing semicolon.
&without one decodes in a browser and does not decode anywhere else. It is flagged above instead of passed over. - References to characters that do not exist. Anything past U+10FFFF, a surrogate, or character zero all become the replacement character, exactly as the HTML parsing specification requires.
Escaping is not sanitising
Escaping these five characters is what stops text being read as markup, and it is the right answer whenever you are putting text into a page. It is not the answer to accepting HTML from somebody else.
A URL attribute is its own context with its own rules. Look athref="javascript:...": not one special character in it, and it runs code. So is anything inside a <script> or a<style>, where entity decoding does not happen and escaping does nothing. If the input is meant to contain markup, the tool for that is a sanitiser with an allowlist, not an escaper.
The non-breaking space, which is no space at all
is U+00A0, and it differs from a space in two ways that matter: a line will not break at one, and a run of them does not collapse the way ordinary whitespace does. Which is why it gets used as a layout tool, and why text copied out of a rendered page arrives full of characters that look like spaces, are not, and break every comparison and every trim.
The Unicode inspector on this site is the tool for finding them once they are already in your data.