DevToolsForYou
Encoding

How HTML Escaping Works

A practical guide to HTML escaping — why it matters for security, which characters to escape, when to use it, and how XSS happens when you skip it.

2 min readUpdated Apr 11, 2026

Why HTML escaping exists

HTML uses certain characters as syntax: < starts a tag, > ends a tag, & starts an entity, and " delimits attribute values. When user-supplied text contains these characters, a browser will interpret them as HTML structure instead of content. HTML escaping replaces these characters with their entity equivalents so they render visually but do not alter the document structure.

The five characters that must always be escaped

text
& → &amp;   (must be first — otherwise double-escaping occurs)
< → &lt;
> → &gt;
" → &quot;   (inside double-quoted attributes)
' → &#39;   (inside single-quoted attributes)

What XSS looks like when you forget

Cross-Site Scripting (XSS) happens when user input is inserted into HTML without escaping. An attacker can inject a script tag that runs arbitrary JavaScript in other users' browsers, stealing cookies, session tokens, or performing actions on their behalf.

javascript
// Vulnerable — directly inserting user input into HTML
div.innerHTML = 'Welcome, ' + username;

// If username = '<script>document.cookie</script>'
// The browser executes the script

// Safe — use textContent for plain text
div.textContent = 'Welcome, ' + username;

// Or escape before inserting as HTML
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

Context matters — escaping is not universal

The correct escaping depends on where you are inserting data. Inside HTML text content: escape <, >, &. Inside an HTML attribute: also escape " (or '). Inside a <script> tag: you need JavaScript string escaping, not HTML escaping. Inside a CSS value: CSS escaping rules apply. Never insert untrusted data directly into inline event handlers (onclick=) or JavaScript URLs (href='javascript:...').

html
<!-- Safe: text node -->
<p>Hello, &lt;b&gt;World&lt;/b&gt;</p>

<!-- Safe: attribute value -->
<input value="He said &quot;hello&quot;">

<!-- UNSAFE: no amount of HTML escaping saves this -->
<script>var name = '{{ userInput }}';</script>
<!-- Use JSON.stringify for JS context: -->
<script>var name = {{ userInput | tojson }};</script>

Let your framework do it

Modern frameworks — React, Vue, Angular, Svelte — escape HTML automatically when you insert values into templates. React's JSX auto-escapes anything in {}. Only use dangerouslySetInnerHTML / v-html when you genuinely need to render HTML, and only with content you control or have sanitised with a library like DOMPurify.

jsx
// React — auto-escaped, safe
<p>Hello, {username}</p>

// React — raw HTML, only for trusted content
<p dangerouslySetInnerHTML={{ __html: sanitisedHtml }} />

// DOMPurify — sanitise before inserting
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHtml);
element.innerHTML = clean;
Frequently asked questions

What is the difference between HTML escaping and URL encoding?

HTML escaping converts characters to HTML entities (&lt; &amp;) so they render safely in an HTML document. URL encoding (percent-encoding) converts characters to %XX sequences so they are safe to transmit in a URL. They are for different contexts and are not interchangeable.

Is escaping output the same as sanitising input?

No. Input sanitisation removes or transforms dangerous content at ingestion time and is hard to do correctly. Output escaping converts characters to their safe representation at render time and is the more reliable defence. Do both: validate and sanitise on input, and always escape on output.

Related cheatsheetsAll cheatsheets →
Related guidesAll guides →