Inserting dynamic, user-generated HTML into your website has historically been one of the easiest ways to introduce cross-site scripting (XSS) vulnerabilities. For years, developers had to choose between using the highly dangerous innerHTML property, writing complex homegrown regex filters, or pulling in heavyweight external libraries like DOMPurify.
The web platform is solving this problem natively with the introduction of the HTML Sanitizer API. This API brings robust, context-aware, and highly optimized sanitization capabilities directly into modern browsers.
The Danger of the innerHTML Trap
When you use the traditional element.innerHTML = untrustedString approach, the browser parses and executes everything inside that string immediately. If an attacker passes a malicious payload, your users are instantly vulnerable:
// A classic XSS vector
const userComment = `<img src="x" onerror="alert('Hacked!')">`;
container.innerHTML = userComment; // The onerror handler executes immediately!
Libraries like DOMPurify solved this by parsing and cleaning the string in JavaScript before it ever touched the DOM. While effective, this introduces third-party dependency overhead and minor execution bottlenecks.
The Modern Way: Native Element.setHTML()
The core of the HTML Sanitizer API is the secure Element.prototype.setHTML() method. Instead of setting raw text, you pass the untrusted input directly to setHTML(), and the browser handles the parsing and sanitization in native code before inserting it into the target element.
1. Simple Safe Insertion (Built-in Defaults)
By default, calling setHTML() will completely strip high-risk elements—such as
const commentsContainer = document.getElementById("comments");
const rawUserInput = `<p>Check this out! <script>maliciousCode();</script></p>`;
// Safe: automatically strips the script tag in native code!
commentsContainer.setHTML(rawUserInput);
2. Reusable Visual Customization
If you need more control, you can construct a custom Sanitizer instance. This allows you to whitelist specific elements or attributes while letting the browser enforce standard XSS safeguards:
// Define a custom config to allow simple rich text
const customSanitizer = new Sanitizer({
allowElements: ["p", "a", "strong", "em", "code"],
allowAttributes: {
href: ["a"],
target: ["a"]
},
allowComments: false
});
// Any scripts or disallowed tags/attributes are stripped automatically
container.setHTML(rawUserInput, { sanitizer: customSanitizer });
Understanding the Unsafe Siblings
The browser sandbox also provides low-level APIs like Element.setHTMLUnsafe() and ShadowRoot.setHTMLUnsafe().
setHTML(): Safe by design. It refuses to bypass standard XSS protections under any configuration (it will always strip<script>, event handlers, etc.).setHTMLUnsafe(): Designed for advanced, expert scenarios. It does not enforce built-in XSS safeguards unless you strictly filter them with a custom sanitizer or pair it with Trusted Types (TrustedHTML). Always prefersetHTML()for untrusted, user-generated inputs.
Guarding Against Evolving Specs and Environments
As of mid-2026, the HTML Sanitizer API is standardizing under the WHATWG HTML Sanitization specification and is widely supported natively in current evergreen browsers. However, to support older platforms or edge-case environments, you should always feature-detect first.
function safeInsertHTML(htmlString, targetElement) {
if (typeof Sanitizer !== "undefined" && "setHTML" in Element.prototype) {
targetElement.setHTML(htmlString);
} else {
// Fallback to DOMPurify for legacy browsers
targetElement.innerHTML = DOMPurify.sanitize(htmlString);
}
}





