Why escaping user input is not enough
When your application needs to insert a user-supplied string into the DOM—say, from a query parameter, an API response, or a cookie—the safest approach is to avoid interpreting any of it as markup. The danger is acute when you write directly into innerHTML, because any unescaped <script> tag or event handler attribute becomes live code.
Escaping special characters does neutralize script execution, but it is a blunt tool. If you convert every < and > to an HTML entity, you also destroy legitimate formatting that the user might have intended. Using textContent is even more restrictive; the string renders as plain text with no HTML interpretation at all. If you need to keep harmless markup like <em> while stripping out anything executable, escaping is the wrong solution.
What sanitizing actually does
Sanitizing is a different operation. Instead of encoding special characters, it takes the input string, parses it as HTML, and removes the parts that are considered dangerous—most notably anything that can trigger script execution. The remaining markup is then safe to insert directly into the document. For example, an <img onerror> tag would be stripped of its event handler, leaving the image element intact but harmless.
The output you want is a DOM tree where elements are preserved but executable content is gone. That requires a parser that understands HTML structure, knows which tags and attributes are risky, and can return the clean result either as nodes or as a string.
An overview of the Sanitizer API
The proposed Sanitizer API brings that capability into the platform. At its simplest, you create a Sanitizer and hand it a string to write into an element with setHTML():
const sanitizer = new Sanitizer();
element.setHTML(userInput);
The { sanitizer: new Sanitizer() } argument is actually the default, so the following is equivalent:
element.setHTML(userInput, { sanitizer: new Sanitizer() });
A key detail is that setHTML() is a method of Element—not a standalone function. Because the target element is known, the API parses the string in that element's context and returns the sanitized result directly. If you need the output as a string, you can read the innerHTML of the element after calling setHTML().
Customizing Sanitizer behavior with configuration
The default configuration removes scripts and event handler attributes, but you can adjust the behavior using an options object passed to the constructor.
Working with elements
Three options control how elements are handled at the top level:
allowElements: the sanitizer keeps these elements.blockElements: these elements are removed, but their children are retained.dropElements: these elements are removed along with their descendants.
Working with attributes
allowAttributes and dropAttributes accept an attribute match list. This is an object where each key is an attribute name and each value is either an array of target element names or the wildcard *.
const sanitizer = new Sanitizer({
allowAttributes: {
'data-id': ['section', 'div'],
},
});
Custom elements
The allowCustomElements option toggles whether unknown custom elements are allowed. Even when set to true, any other element and attribute restrictions you specify will also apply to those custom elements.
Comparing the Sanitizer API with DOMPurify
The popular DOMPurify library has long served this need. The most important functional difference is in the return type. DOMPurify.sanitize() returns a string; you then assign that string to an element using innerHTML.
That design leads to two awkward consequences. First, the input is parsed twice: once by DOMPurify and again by the browser when the string is assigned to innerHTML. This is wasted work, and there are edge cases where the two parsing passes can produce different results—a potential gap that could be exploited. Second, DOMPurify.sanitize() operates on a plain string with no knowledge of where the content will be inserted. Since HTML parsing rules depend on context—a <td> is valid in a <table> but not in a <div>—the sanitizer must guess.
By operating on an Element and doing a single parse internally, the Sanitizer API addresses both of those issues. DOMPurify remains useful as a fallback in browsers where the API is not yet available.
Implementation status in browsers
The Sanitizer API is in the WICG standardization process. Chrome is implementing it; feature tracking is available on the Chrome platform status page. Mozilla considers the proposal worth prototyping and is actively working on it. WebKit has responded on its mailing list regarding the proposal.
Chrome supports the API as of version 146, with Firefox following in 148. To enable it manually on Chrome 93 and later, use the flag about://flags/#enable-experimental-web-platform-features. For older Chrome Canary and Dev builds, pass --enable-blink-features=SanitizerAPI on the command line. In Firefox, set dom.security.sanitizer.enabled to true in about:config.
Because the feature may not be available everywhere, use feature detection before calling the API. You can also check for the presence of Sanitizer on the global object to branch to a fallback library.
Providing feedback
Feedback from real-world usage helps drive the spec forward. If you find unexpected behavior or bugs in Chrome's implementation, you can file a bug at new.crbug.com, selecting the Blink>SecurityFeature>SanitizerAPI components. General feedback and discussions with spec authors happen on the Sanitizer API GitHub issues page. For a hands-on demonstration, the Sanitizer API Playground by Mike West lets you test configurations directly in your browser.



