HTML Entities Explained: Named vs Numeric, the Five You Must Escape, and When You Need None

An HTML entity, formally a character reference, is a way of writing a character as text that starts with `&` and usually ends with `;`: `&lt;` for `<`, `&copy;` for `©`, `&#8364;` for `€`. Entities once solved two problems: characters that have meaning in HTML markup, and characters that could not be typed or stored in the page's encoding. UTF-8 has removed the second problem almost entirely. The first one, escaping markup characters, is still one of the most important things a web application does.

Three ways to write the same character

named          &copy;      &euro;      &lt;      &nbsp;
decimal        &#169;      &#8364;     &#60;     &#160;
hexadecimal    &#xA9;      &#x20AC;    &#x3C;    &#xA0;
renders as     ©           €           <         (non-breaking space)

Numeric references use the character's Unicode code point, so any character can be written that way: &#x1F600; is 😀. Named references are easier to read but exist only for a fixed list. The HTML Standard defines over 2,000 names, from &amp; to &TripleDot;. XML, and therefore SVG and XHTML served as XML, predefines only five: &amp;, &lt;, &gt;, &quot; and &apos;. Use numeric references for anything else there.

The characters you must escape

Only a handful of characters have to be escaped in HTML, and which ones depends on where the text goes.

&   ->  &amp;     always: it starts a character reference
<   ->  &lt;      in text: it starts a tag
>   ->  &gt;      not strictly required, but escaped by convention
"   ->  &quot;    inside double-quoted attribute values
'   ->  &#39;     inside single-quoted attribute values (&apos; is HTML5/XML only)

Escaping these five in all text and attribute values is what standard template engines do by default: Blade's {{ }}, Twig, Jinja2 with autoescape, Django templates, ERB with Rails, Go's html/template, and React's JSX. It is the main defence against cross-site scripting (XSS). A comment containing <script> is displayed as text instead of running.

<?php
// PHP: escape for HTML text and attributes (ENT_QUOTES | ENT_SUBSTITUTE is the default since PHP 8.1)
echo htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');

Escape & first when doing it by hand. Otherwise the & in freshly created &lt; is escaped again. Better still, use the built-in function and never replace characters with string operations.

When you do not need entities

With <meta charset="utf-8">, the default for new documents, you can type ©, €, é, —, → and emoji directly into HTML. Writing &eacute; for é adds nothing except bytes and reduced readability. Entities for ordinary characters are a habit from the days of Latin-1 pages and email templates, not a requirement.

They remain useful for invisible or ambiguous characters, where seeing the code in the source is the point: &nbsp; (non-breaking space), &shy; (soft hyphen, an optional break point in long words), &zwj; and &zwnj; (zero-width joiner and non-joiner), &lrm; and &rlm; (text-direction marks), and &#8203; (zero-width space). A reviewer cannot see these characters when typed directly.

&nbsp; is not a spacing tool

&nbsp; is a space that prevents a line break. Use it to keep things together: 10&nbsp;km, Mr&nbsp;Smith, © 2026&nbsp;DevToolGrid. Using runs of &nbsp;&nbsp;&nbsp; to indent or align text breaks on other screen sizes and fonts, and screen readers may announce the characters. Use CSS margins, padding or white-space instead.

Non-breaking spaces also cause bugs when copied. Text pasted from web pages and word processors contains U+00A0 characters that look like spaces but do not match ' ' in code, do not split in str.split(' '), and make string comparisons fail. In JavaScript, \s in a regex matches them; a literal space does not.

Escaping depends on the context

HTML entity escaping is correct only for HTML text and quoted attribute values. Other contexts inside an HTML page need different escaping, and using entities there is either useless or insufficient.

<p>USER_TEXT</p>                         HTML escape
<input value="USER_TEXT">                HTML escape (quoted attribute)
<a href="/search?q=USER_TEXT">           URL-encode the value, then HTML escape
<script>const name = USER_TEXT;</script> JSON-encode (json_encode / JSON.stringify),
                                         and escape </ as <\/ or use a data attribute
<div style="color: USER_TEXT">           avoid; validate against an allow-list
<a href="USER_URL">                      allow only http:/https: - javascript: URLs
                                         pass HTML escaping untouched

The last line is a classic XSS bug. javascript:alert(1) contains no characters that HTML escaping changes, so an escaped link still executes when clicked. Validate the URL scheme as well.

Double escaping and missing semicolons

If text is escaped twice, the user sees the entity itself: Tom &amp;amp; Jerry displays as Tom &amp; Jerry. This happens when data is escaped before it is stored and escaped again when rendered, or when a template escapes a value that a helper already escaped. The rule is to store raw text and escape once, at output, for the context it is written into.

Browsers still accept some named references without the final semicolon, for historical reasons. &copy 2026 renders as © 2026. Inside attribute values the rules are stricter: a legacy reference followed by = or an alphanumeric character is not decoded, which is why href="?a=1&copy=2" keeps its &copy= intact. Relying on this is fragile. Always write the semicolon, and write a literal & in URLs as &amp; in HTML source. The browser decodes it back before following the link.

Entities in React, Vue and email

JSX escapes strings automatically, and named entities written directly in JSX text (&nbsp;, &copy;) are converted at compile time. Inside a JavaScript string, however, '&copy;' is just six characters and renders literally. Use the character itself or '\u00A9'. dangerouslySetInnerHTML in React and v-html in Vue switch escaping off, so their input must be sanitised with a library such as DOMPurify.

HTML email is where entities are still common, because some email clients mishandle the declared charset. Numeric references for non-ASCII characters are the most robust choice there.

Sources and further reading

The WHATWG HTML Standard defines character references and the full list of named references, including the legacy no-semicolon rules. The XML 1.0 specification defines its five predefined entities. The OWASP Cross Site Scripting Prevention Cheat Sheet covers context-specific output encoding. The PHP manual documents htmlspecialchars and its default flags.

Frequently asked questions

What are HTML entities?

Text sequences that represent a character, starting with `&` and ending with `;`, such as `&lt;` for `<` or `&#169;` for `©`. They are used to escape characters that have a meaning in HTML and to write characters that are hard to type or see.

Which characters must be escaped in HTML?

`&` and `<` in text, plus `"` or `'` inside attribute values quoted with that character. Escaping `>` as well is conventional. Template engines escape all five automatically.

What is the difference between named and numeric entities?

Named entities use a mnemonic from the HTML list (`&euro;`), while numeric ones use the Unicode code point in decimal (`&#8364;`) or hex (`&#x20AC;`). Numeric references work for every character and in XML; named ones are limited to the defined list.

Do I still need HTML entities with UTF-8?

Only for the markup characters `&`, `<`, `>` and quotes, and optionally for invisible characters such as `&nbsp;` or `&shy;`. Characters such as é, € and © can be typed directly in a UTF-8 page.

Why does my page show &amp;amp;?

The text was escaped twice, for example once before saving to the database and once in the template. Store raw text and escape only once when rendering.

Is &apos; valid HTML?

Yes in HTML5 and XML, but not in HTML 4, and very old clients may not recognise it. `&#39;` works everywhere and is what most escaping functions output.