Developer

HTML Entities, and the Five Characters That Actually Need One

An entity is a character written in plain ASCII. You need five of them, and the rest you can type straight into the file.

An HTML entity is a way of writing a character using only plain ASCII, so the browser renders that character instead of reading it as markup. & produces an ampersand, < produces a less-than sign, — produces an em dash. On a UTF-8 page — which is every page now — only five characters ever need it, and only two of them need it everywhere. Accents, arrows, currency symbols and emoji you can type directly into the file, and the entities you see for them in old tutorials are left over from an era when you could not.

The three ways to write the same character

Every entity is an entity reference: an ampersand, a name or a number, and a semicolon. There are three forms, and browsers treat them as identical.

All three of those ampersand forms produce the same character. Numeric references exist for anything Unicode can express; named ones only for the shortlist, which is why escapers fall back to numbers. The encoder here keeps a deliberately short list — around seventy names that old parsers and mail clients reliably know — and writes everything else as a number.

Do you need the closing semicolon?

HTML5 decodes roughly a hundred older named references even without the closing semicolon, for compatibility with pages written in 1998. So &copy in body text renders as ©, whether you meant it to or not. This is a trap, not a feature: XML, SVG and RSS parsers reject the same thing outright, and XML only defines five named entities at all — amp, lt, gt, quot and apos. Write the semicolon.

Which characters do you actually have to escape?

Five, and only because the parser would otherwise read them as something other than text.

The ampersand case is the one people argue about. AT&T renders fine, because &T is not a reference. Salt & Pepper is fine too. But a query string like ?page=2&copy=full contains &copy, and in visible text that becomes ?page=2©=full. Inside an attribute value HTML5 has a special rule that saves you — a name without a semicolon followed by = or a letter or digit is left alone — which is exactly the kind of exception you do not want to be relying on. Escape the ampersand and stop thinking about it.

Doing that by eye over a block of text is slow and easy to get half right. An escaper with a minimal mode converts those five and leaves everything else as real characters, which is the correct output for a UTF-8 page and keeps the source readable.

What you can stop escaping

If your document declares <meta charset="utf-8"> and is actually saved as UTF-8, type the character. An em dash, a Turkish ı, a ₹, a 🙂 — all of them are fine as literal text, and all of them are shorter, more readable and less error-prone than &#8212; and friends.

Entities are still the better choice for characters that are invisible or ambiguous in source, because nobody can review what they cannot see:

The non-breaking space is worth knowing properly. Regular spaces collapse: ten of them in a row render as one. A non-breaking space does not collapse, and does not allow a line break. That is why 5&nbsp;kg never ends up with the 5 on one line and the kg on the next, and it is the correct fix for a number split from its unit. Writing one on purpose is the easy case. Finding the ones somebody else left in your data is the harder one, and a different job.

Why does my page show &amp; instead of &?

Because something escaped text that was already escaped. The ampersand in &amp; got escaped a second time into &amp;amp;, and the browser faithfully decodes one layer and prints the rest.

It is a pipeline problem, not a typo. A form escapes input on save, the template escapes it again on render, and the second pass cannot tell that the first one happened. The damage compounds predictably, so decoding the string repeatedly until it stops changing tells you how many layers are there. Then remove the escape step that should not be running. Escaping once, at the point of output, is the only arrangement that does not eventually produce this.

Entities, URL encoding and JSON escaping: which goes where?

Three different escaping systems, three different alphabets, applied at three different moments. A space is &#32; in HTML, %20 in a URL and \u0020 in JSON, and using one where another belongs produces text that looks almost right and is broken.

The overlap that causes the most damage is a link. The href is HTML, so it gets entity-escaped; the thing inside it is a URL, so its own reserved characters need percent-encoding first. Both, in that order. If you have ever watched a link break the moment someone put a space or a plus sign in a parameter, the rules behind percent-encoding are the half of the answer that entities do not cover.

JSON is its own world again. HTML entities mean nothing inside a JSON string — they are just characters — and JSON's \u escapes mean nothing to an HTML parser. When a response arrives as one unbroken line of escaped sequences, formatting it before you try to read it will tell you in seconds whether the problem is the data or the encoding.

Is escaping enough to stop XSS?

In HTML text and in quoted attribute values, escaping those five is what stops injected markup from being markup. Outside those two places it buys you much less than it looks like it does. Escaping makes markup display as text; it does not remove anything, and it is correct only for the context you are writing into. Four places where it is not enough:

And if you want to keep some tags and drop others — allow <b>, reject <script> — that is sanitising, and it needs a real parser with an allowlist. Escaping is all-or-nothing by design.

Why did my emoji come out as two broken entities?

Characters above U+FFFF — emoji, rarer CJK, most historic scripts — are stored in JavaScript as two code units. An escaper that walks a string one index at a time emits two references like &#55357;&#56832;, and no parser can put those back together; HTML turns each half into a replacement character. A correct escaper iterates by code point, so a grinning face becomes the single reference &#x1F600;. If you have ever seen a row of black diamonds where an emoji should be, this is usually why.

Numeric references between &#128; and &#159; misbehave in a different way. HTML parsers deliberately do not render those as the Unicode control characters they name; they map them to the Windows-1252 characters authors meant in the 1990s, so &#151; comes out as an em dash. Correct per the standard, entirely unobvious, and one more reason to type the character rather than the number.

If you are staring at a string full of &amp; right now, the entity encoder and decoder here will run it back the other way, and decoding again until the text stops changing tells you how many layers got applied. It also does the forward direction, with the minimal mode you want for anything going into a UTF-8 page. One thing it will not tell you: an entity name it does not recognise comes out unchanged rather than flagged, exactly as a browser would render it, so a misspelled name looks like it decoded fine.

Entities are one instance of a wider trick: taking something a parser would choke on and rewriting it in a safe alphabet. Inlining an image as Base64 is the same idea applied to binary, with the same catch — it is bigger than the thing it replaces, and it is worth knowing when that trade is a bad one.

Frequently asked questions

What are HTML entities?

They are characters written in plain ASCII so a browser renders them instead of reading them as markup. Each one starts with an ampersand and ends with a semicolon, and can use a name (&amp;), a decimal number (&#38;) or a hex number (&#x26;). All three of those examples produce an ampersand.

Which characters do I have to escape in HTML?

Ampersand and less-than always, greater-than by convention, and the quote character you wrapped an attribute value in. On a UTF-8 page nothing else is required — accented letters, dashes, currency symbols and emoji can be typed directly into the source.

Why is my page showing &amp; instead of &?

The text was escaped twice, so the ampersand in the first entity got escaped again. It is usually two stages of a pipeline both doing the job, such as a form escaping on save and a template escaping on render. Fix it by escaping once, at the point of output.

Is &nbsp; the same as a normal space?

No. A non-breaking space is U+00A0: it does not collapse when several appear in a row, and it does not allow a line break. That makes it the right way to keep a number attached to its unit, and the wrong way to indent or space out a layout.

Are HTML entities the same as URL encoding?

No, they are separate systems applied at different moments. A space is &#32; in HTML and %20 in a URL, and JSON uses \u0020 for the same character. A link needs both: the URL is percent-encoded first, then the whole attribute is entity-escaped.

Last updated September 19, 2026