Let’s Explore the New DOM API: setHTML

Let’s Explore the New DOM API: setHTML

D
dongAuthor
5 min read

When developing for the web, you often need to insert HTML dynamically. The most common method is innerHTML, but it comes with a significant drawback—it is vulnerable to Cross-Site Scripting (XSS) attacks. To counter this, many developers have relied on external libraries like DOMPurify to safely insert HTML.

Now, there’s a promising new way to safely insert HTML, natively supported by web standards. This is thanks to the introduction of the new DOM API, setHTML(). In this post, we’ll dive into what setHTML() is, why it’s needed, and how to use it.

What is setHTML()?

setHTML() is a new method on the Element interface that safely sets an HTML string on a DOM element, as the name suggests. Beyond just inserting HTML, it uses the built-in HTML Sanitizer API to parse and sanitize the string.

Key features of setHTML() include:

  • XSS Prevention: It automatically removes potentially dangerous elements like <script> tags or inline event handlers such as onerror from the provided HTML string.
  • Validation: It removes invalid elements that don’t fit the context of the current element.
  • Default Sanitizer: It can safely insert HTML with default sanitizer settings, even without any configuration.

According to the MDN documentation, setHTML() not only strips disallowed HTML entities but also removes unsafe elements or attributes even if they’re allowed by the sanitizer configuration. This allows developers to focus more on business logic with less concern about security.

Why Should You Use setHTML()?

You might wonder why setHTML() is necessary when innerHTML already exists. Let’s compare the two and explore what advantages setHTML() brings.

Comparison with innerHTML

While innerHTML is simple to use, its biggest issue is security. If you assign untrusted strings from external sources directly to innerHTML, malicious scripts inserted by attackers can be executed.

const userInput = `<img src=x onerror="alert('XSS attack!')">`;
const container = document.getElementById('container');

// A script included in userInput will execute and show an alert.
container.innerHTML = userInput;

In contrast, setHTML() completely blocks this risk.

const userInput = `<img src=x onerror="alert('XSS attack!')">`;
const container = document.getElementById('container');

// The onerror attribute is removed by the sanitizer, so the script does not run.
container.setHTML(userInput);

Comparison with setHTMLUnsafe()

Alongside setHTML(), there is also a method called setHTMLUnsafe(). As the name implies, this method does not sanitize the HTML. Unless you’re handling a very specific case where XSS-vulnerable elements must be included, using setHTML() is strongly recommended. setHTMLUnsafe() should only be used for HTML from trusted sources.

In conclusion, setHTML() is a secure alternative to innerHTML that maintains its convenience while significantly enhancing security.

Element: setHTMLUnsafe() method

How to Use setHTML()

The basic usage of setHTML() is very straightforward!

// Basic usage
const unsanitizedString = "This is <b>safe</b> HTML. <script>alert('This won’t run!')</script>";
const targetElement = document.getElementById("target");

// Insert HTML using the default sanitizer
targetElement.setHTML(unsanitizedString);

When this code runs, the <script> tag is removed, and only the text “This is safe HTML.” is rendered.

Customizing with options.sanitizer

setHTML() accepts an options object as a second argument, allowing you to control how the sanitizer works. Using the options.sanitizer property, you can explicitly define which tags and attributes should be allowed or removed.

const unsanitizedString = "<div>div tag</div> <p>p tag</p> <button>button tag</button>";
const targetElement = document.getElementById("target");

// 1. Create and pass a Sanitizer object
// Only allow div, p, and button tags
const customSanitizer = new Sanitizer({
  elements: ["div", "p", "button"],
});
targetElement.setHTML(unsanitizedString, { sanitizer: customSanitizer });

// 2. Pass a SanitizerConfig object inline
// Remove div and p tags
targetElement.setHTML(unsanitizedString, {
  sanitizer: { removeElements: ["div", "p"] },
});

When using SanitizerConfig, be careful not to pass conflicting configurations like defining both allowElements and removeElements, as this can cause a TypeError.

What About Browser Support?

Unfortunately, setHTML() is still an experimental feature and, at the time of writing, is not supported by most major browsers. It is enabled by default in the Firefox Nightly version, and is expected to gradually roll out to other browsers.

Therefore, before using it in a production environment, be sure to check the MDN browser compatibility chart. Until then, you can replicate similar functionality with the Sanitizer API Polyfill.

Community Reception

The arrival of setHTML() has received positive responses from many developer communities. On forums like Hacker News, the sentiment is: “It finally came after 25 years!”

  • Reduced Library Dependencies: A developer from the lightweight Lit library expressed hope that once setHTML() becomes standard, it could replace the unsafeHTML directive and enable safe HTML rendering without relying on external dependencies like DOMPurify. Although DOMPurify is powerful, it’s several times larger than the lit-html core, so having a built-in API is a major win.
  • A Safe Alternative to innerHTML: Many developers see setHTML() as a secure version of innerHTML or a built-in replacement for DOMPurify commonly used via npm. This marks a significant step forward in the security capabilities of the web platform.

Hacker news

Personally, This is Great News!

setHTML() is a crucial API that addresses the long-standing security issues of innerHTML and provides a secure method to insert HTML via a web-standard API. While still in an experimental phase, if this feature gets officially adopted by all major browsers, developers will be able to reduce reliance on third-party libraries and build safer, more robust web applications.

Although it might be difficult to apply it in production right away, being aware of and testing this new API now will help you prepare for the future. Let’s look forward to the safer web development environment that setHTML() promises.

Personally, since this blog often deals with raw HTML, this is really exciting news! 🤣
I hope it stabilizes soon! 🥹

References

Let’s Explore the New DOM API: setHTML | devdong