Element: setHTMLUnsafe() method
Baseline 2025Newly available
Since September 2025, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.
Warning:This method parses its input as HTML, writing the result into the DOM.APIs like this are known asinjection sinks, and are potentially a vector forcross-site scripting (XSS) attacks, if the input originally came from an attacker.
You can mitigate this risk by always passingTrustedHTML objects instead of strings andenforcing trusted types.SeeSecurity considerations for more information.
Note:Element.setHTML() should almost always be used instead of this method — on browsers where it is supported — as it always removes XSS-unsafe HTML entities.
ThesetHTMLUnsafe() method of theElement interface is used to parse HTML input into aDocumentFragment, optionally filtering out unwanted elements and attributes, and those that don't belong in the context, and then using it to replace the element's subtree in the DOM.
In this article
Syntax
setHTMLUnsafe(input)setHTMLUnsafe(input, options)Parameters
inputA
TrustedHTMLinstance or string defining HTML to be parsed.optionsOptionalAn options object with the following optional parameters:
sanitizerOptionalA
SanitizerorSanitizerConfigobject that defines what elements of the input will be allowed or removed.This can also be a string with the value"default", which applies aSanitizerwith the default (XSS-safe) configuration.If not specified, no sanitizer is used.Note that generally a
Sanitizeris expected to be more efficient than aSanitizerConfigif the configuration is to reused.
Return value
None (undefined).
Exceptions
TypeErrorThis is thrown if:
inputis passed a string whenTrusted Types areenforced by a CSP and no default policy is defined.options.sanitizeris passed a:SanitizerConfigthat isn'tvalid.For example, a configuration that includes both "allowed" and "removed" configuration settings.- string that does not have the value
"default". - value that is not a
Sanitizer,SanitizerConfig, or string.
Description
ThesetHTMLUnsafe() method is used to parse an HTML input into aDocumentFragment, optionally sanitizing it of unwanted elements and attributes, and discarding elements that the HTML specification doesn't allow in the target element (such as<li> inside a<div>).TheDocumentFragment is then used to replace the element's subtree in the DOM.
Unlike withElement.innerHTML,declarative shadow roots in the input will be parsed into the DOM.If the string of HTML defines more than onedeclarative shadow root in a particular shadow host then only the firstShadowRoot is created — subsequent declarations are parsed as<template> elements within that shadow root.
setHTMLUnsafe() doesn't perform any sanitization by default.If no sanitizer is passed as a parameter, all HTML entities in the input will be injected.It is therefore potentially even less safe thatElement.innerHTML, which disables<script> execution when parsing.
Security considerations
The suffix "Unsafe" in the method name indicates that it does not enforce removal of all XSS-unsafe HTML entities (unlikeElement.setHTML()).While it can do so if used with an appropriate sanitizer, it doesn't have to use an effective sanitizer, or any sanitizer at all!The method is therefore a possible vector forcross-site scripting (XSS) attacks, where potentially unsafe strings provided by a user are injected into the DOM without first being sanitized.
You should mitigate this risk by always passingTrustedHTML objects instead of strings, andenforcing trusted types using therequire-trusted-types-for CSP directive.This ensures that the input is passed through a transformation function, which has the chance tosanitize the input to remove potentially dangerous markup (such as<script> elements and event handler attributes), before it is injected.
UsingTrustedHTML makes it possible to audit and check that sanitization code is effective in just a few places, rather than scattered across all your injection sinks.You should not have to pass a sanitizer to the method when usingTrustedHTML.
If for any reason you can't useTrustedHTML (or even better,setHTML()) then the next safest option is to usesetHTMLUnsafe() with the XSS-safe defaultSanitizer.
When shouldsetHTMLUnsafe() be used?
setHTMLUnsafe() should almost never be used ifElement.setHTML() is available, because there are very few (if any) cases where user-provided HTML input should need to include XSS-unsafe elements.Not only issetHTML() safe, but it avoids having to consider trusted types.
UsingsetHTMLUnsafe() might be appropriate if:
You can't use
setHTML()or trusted types (for whatever reason) and you want to have the safest possible filtering.In this case you might usesetHTMLUnsafe()with the defaultSanitizerto filter all XSS-unsafe elements.You can't use
setHTML()and the input might contain declarative shadow roots, so you can't useElement.innerHTML.You have an edge case where you have to allow HTML input that includes a known set of unsafe HTML entities.
You can't use
setHTML()in this case, because it strips all unsafe entities.You could usesetHTMLUnsafe()without a sanitizer orinnerHTML, but that would allow all unsafe entities.A better option here is to call
setHTMLUnsafe()with a sanitizer that allows just those dangerous elements and attributes we actually need.While this is still unsafe, it is safer than allowing all of them.
For the last point, consider a situation where your code relies on being able to use unsafeonclick handlers.The following code shows the effect of the different methods and sanitizers for this case.
const target = document.querySelector("#target");const input = "<img src=x onclick=alert('onclick') onerror=alert('onerror')>";// Safe - removes all XSS-unsafe entities.target.setHTML(input);// Removes no event handler attributestarget.setHTMLUnsafe(input);target.innerHTML = input;// Safe - removes all XSS-unsafe entities.const configSafe = new Sanitizer();target.setHTMLUnsafe(input, { sanitizer: configSafe });// Removes all XSS-unsafe entities except `onclick`const configLessSafe = new Sanitizer();config.allowAttribute("onclick");target.setHTMLUnsafe(input, { sanitizer: configLessSafe });Examples
>setHTMLUnsafe() with Trusted Types
To mitigate the risk of XSS, we'll first create aTrustedHTML object from the string containing the HTML, and then pass that object tosetHTMLUnsafe().Since trusted types are not yet supported on all browsers, we define thetrusted types tinyfill.This acts as a transparent replacement for the Trusted Types JavaScript API:
if (typeof trustedTypes === "undefined") trustedTypes = { createPolicy: (n, rules) => rules };Next we create aTrustedTypePolicy that defines acreateHTML() for transforming an input string intoTrustedHTML instances.Commonly implementations ofcreateHTML() use a library such asDOMPurify to sanitize the input as shown below:
const policy = trustedTypes.createPolicy("my-policy", { createHTML: (input) => DOMPurify.sanitize(input),});Then we use thispolicy object to create aTrustedHTML object from the potentially unsafe input string:
// The potentially malicious stringconst untrustedString = "abc <script>alert(1)<" + "/script> def";// Create a TrustedHTML instance using the policyconst trustedHTML = policy.createHTML(untrustedString);Now that we havetrustedHTML, the code below shows how you can use it withsetHTMLUnsafe().The input has been through the transformation function, so we don't pass a sanitizer to the method.
// Get the target Element with id "target"const target = document.getElementById("target");// setHTMLUnsafe() with no sanitizertarget.setHTMLUnsafe(trustedHTML);Using setHTMLUnsafe() without Trusted Types
This example demonstrates the case where we aren't using trusted types, so we'll be passing sanitizer arguments.
The code creates an untrusted string and shows a number of ways a sanitizer can be passed to the method.
// The potentially malicious stringconst untrustedString = "abc <script>alert(1)<" + "/script> def";// Get the target Element with id "target"const target = document.getElementById("target");// Define custom Sanitizer and use in setHTMLUnsafe()// This allows only elements: div, p, button, scriptconst sanitizer1 = new Sanitizer({ elements: ["div", "p", "button", "script"],});target.setHTMLUnsafe(untrustedString, { sanitizer: sanitizer1 });// Define custom SanitizerConfig within setHTMLUnsafe()// Removes the <script> element but allows other potentially unsafe entities.target.setHTMLUnsafe(untrustedString, { sanitizer: { removeElements: ["script"] },});setHTMLUnsafe() live example
This example provides a "live" demonstration of the method when called with different sanitizers.The code defines buttons that you can click to inject a string of HTML.One button injects the HTML without sanitizing it at all, and the second uses a custom sanitizer that allows<script> elements but not other unsafe items.The original string and injected HTML are logged so you can inspect the results in each case.
Note:Because we want to show how the sanitizer argument is used, the following code injects a string rather than a trusted type.You should not do this in production code.
HTML
The HTML defines two<button> elements for calling the method with different sanitizers, another button to reset the example, and a<div> element to inject the string into.
<button type="button">None</button><button type="button">allowScript</button><button type="button">Reload</button><div>Original content of target element</div><pre></pre>#log { height: 320px; overflow: scroll; padding: 0.5rem; border: 1px solid black; margin: 5px;}JavaScript
const logElement = document.querySelector("#log");function log(text) { logElement.textContent += text;}if ("Sanitizer" in window) {First we define the string to sanitize, which will be the same for all cases.This contains the<script> element and theonclick handler, both of which are considered XSS-unsafe.We also define the handler for the reload button.
// Define unsafe string of HTMLconst unsanitizedString = ` <div> <p>Paragraph to inject into shadow DOM. <button>Click me</button> </p> <script src="path/to/a/module.js" type="module"><\/script> <p data-id="123">Para with <code>data-</code> attribute</p> </div>`;const reload = document.querySelector("#reload");reload.addEventListener("click", () => document.location.reload());Next we define the click handler for the button that sets the HTML with no sanitizer.Generally we would expect the method to drop elements in the string that aren't allowed in the context (such as table-specific elements in a<div> element), but otherwise match the input string.In this case the strings should match.
const buttonNoSanitizer = document.querySelector("#buttonNoSanitizer");buttonNoSanitizer.addEventListener("click", () => { // Set unsafe HTML without specifying a sanitizer target.setHTMLUnsafe(unsanitizedString); // Log HTML before sanitization and after being injected logElement.textContent = "No sanitizer: string should be injected without filtering\n\n"; log(`\nunsanitized: ${unsanitizedString}`); log(`\n\nsanitized: ${target.innerHTML}`);});The next click handler sets the target HTML using a custom sanitizer that allows only<div>,<p>, and<script> elements.Note that because we're using thesetHTMLUnsafe() method,<script> are not removed!
const allowScriptButton = document.querySelector("#buttonAllowScript");allowScriptButton.addEventListener("click", () => { // Set the content of the element using a custom sanitizer const sanitizer1 = new Sanitizer({ elements: ["div", "p", "script"], }); target.setHTMLUnsafe(unsanitizedString, { sanitizer: sanitizer1 }); // Log HTML before sanitization and after being injected logElement.textContent = "Sanitizer: {elements: ['div', 'p', 'script']}\n"; log(`\nunsanitized: ${unsanitizedString}`); log(`\n\nsanitized: ${target.innerHTML}`);});} else { log("The HTML Sanitizer API is NOT supported in this browser."); // Provide fallback or alternative behavior}Results
Click the "None" and "allowScript" buttons to see the effects of no sanitizer and a custom sanitizer, respectively.
When you click the "None" button, you should see that the input and output match, as no sanitizer is applied.When you click the "allowScript" button the<script> element is still present, but the<button> element is removed.With this approach you can create safe HTML, but you aren't forced to.
Specifications
| Specification |
|---|
| HTML> # dom-element-sethtmlunsafe> |