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.
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
& → & (must be first — otherwise double-escaping occurs)
< → <
> → >
" → " (inside double-quoted attributes)
' → ' (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.
// 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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}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:...').
<!-- Safe: text node -->
<p>Hello, <b>World</b></p>
<!-- Safe: attribute value -->
<input value="He said "hello"">
<!-- 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.
// 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;What is the difference between HTML escaping and URL encoding?
HTML escaping converts characters to HTML entities (< &) 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.
How to Encode and Decode Base64
A practical guide to Base64 encoding and decoding — understand when and why to use it, how to handle Unicode, and how to do it in JavaScript, Python, and the command line.
Read guide →How to URL Encode and Decode
A practical guide to percent-encoding — understand which characters need encoding, how the % notation works, and how to encode and decode URLs in JavaScript, Python, and the terminal.
Read guide →