Using innerHTML Safely in JavaScript DOM Methods

innerHTML lets you insert HTML into a page, but it carries a security risk. Learn how it works, the XSS danger, and safer alternatives for dynamic content.

4 min read

innerHTML safety in JavaScript comes down to one rule: the property lets you read or replace the HTML inside an element using a string, and it is powerful, readable, and convenient. It is also the most dangerous DOM property when used carelessly.

Understanding both its power and its risk is essential before you use it.

javascriptjavascript
const container = document.querySelector("#content");
container.innerHTML = "<h2>Welcome</h2><p>This paragraph was inserted.</p>";

The container now contains a heading and a paragraph, parsed from the string and rendered as live DOM elements. In one line, you inserted structured HTML. This is why innerHTML is popular: it is concise and does exactly what you expect.

The Security Risk: Cross-Site Scripting (XSS)

The danger comes from where the HTML string originates. If any part of that string comes from user input, an attacker can inject elements that run their own script the moment the browser parses them.

A literal script element inserted through innerHTML will not run, browsers block that one specific case. But plenty of other elements execute code through event handler attributes, and innerHTML parses those just as readily:

javascriptjavascript
const userName = '<img src="x" onerror="stealCookies()">';
const greeting = document.querySelector("#greeting");
 
// Dangerous: the broken image triggers onerror and runs the attacker's code
greeting.innerHTML = "Hello, " + userName;

If userName comes from a URL parameter, a form field, or any user-controlled source, the browser tries to load the broken image, fails immediately, and fires the onerror handler. The attacker's code runs with the full privileges of your page.

The Safe Alternatives

For plain text, use textContent instead. It treats everything as text, so HTML tags in the input are displayed literally rather than parsed into real elements:

javascriptjavascript
const userName = '<img src="x" onerror="stealCookies()">';
const greeting = document.querySelector("#greeting");
 
// Safe: displays the tag as literal text, no image element is ever created
greeting.textContent = "Hello, " + userName;

The page now shows the angle brackets and attributes as plain text, not as a real img element. Nothing loads and nothing executes because textContent never parses HTML tags.

For building new elements programmatically, use createElement and appendChild. This approach never touches HTML strings, so there is nothing to inject. Start by clearing the container and creating each element separately:

javascriptjavascript
const greeting = document.querySelector("#greeting");
greeting.textContent = "";
 
const heading = document.createElement("h2");
heading.textContent = "Welcome";
 
const paragraph = document.createElement("p");
paragraph.textContent = "This was built with createElement.";

Neither createElement call ever touches an HTML string, and textContent sets each element's text safely. Now attach both new elements to the page:

javascriptjavascript
greeting.appendChild(heading);
greeting.appendChild(paragraph);

This pattern is completely immune to HTML injection because no string is ever parsed as markup. Every element and every piece of text is built and attached through direct DOM method calls instead.

When innerHTML Is the Right Choice

innerHTML is safe when the HTML string comes entirely from your own code with no user input mixed in. Common safe uses include:

  • Setting up initial page structure from JavaScript
  • Rendering static template content
  • Inserting server-generated HTML that you trust
  • Quickly clearing an element by setting innerHTML to an empty string
javascriptjavascript
// Safe: the string is developer-written with no user input
const template = `
  <div class="card">
    <h3>Static Title</h3>
    <p>This content is hardcoded.</p>
  </div>
`;
document.querySelector("#app").innerHTML = template;

The key rule is simple: if any part of the string came from a user, a URL, an API response you cannot fully trust, or any external source, do not pass it to innerHTML. When you do control the full string, innerHTML is often the fastest way to render a batch of markup in one step, rather than building each element by hand.

What Happens to Existing Content

Setting innerHTML replaces everything inside the element, not just the visible text. This includes existing text nodes, child elements, and any event listeners that were attached to those children:

javascriptjavascript
const container = document.querySelector("#content");
container.innerHTML = "<p>New content</p>";

All previous content and event listeners inside the container are gone. The browser destroys the old DOM subtree and builds a new one from the HTML string. If you need to preserve existing content or event listeners, use insertAdjacentHTML or build elements with createElement instead.

Where to Go Next

Now that you understand the safety rules, learn how to change text content safely with textContent or how to create DOM elements programmatically with createElement. For adding new HTML alongside existing content, see appending elements to the DOM.

Rune AI

Rune AI

Key Insights

  • innerHTML parses HTML strings and inserts them as live DOM elements.
  • Never pass raw user input to innerHTML; it enables XSS attacks.
  • Use textContent for plain text, createElement for building new elements.
  • innerHTML is safe for static, developer-written HTML strings.
  • Overwriting innerHTML removes existing content and event listeners.
RunePowered by Rune AI

Frequently Asked Questions

Why is innerHTML dangerous?

innerHTML parses any HTML string you give it into real elements and attributes. If user input reaches it, an attacker can inject elements with event handler attributes, such as an image with a broken src and an onerror handler, that run their own script the moment the browser parses it. This is called cross-site scripting (XSS) and can steal data, hijack sessions, or deface pages. Note that an actual script element inserted through innerHTML does not execute, browsers block that specific case, but many other elements and attributes still do.

When is it safe to use innerHTML?

innerHTML is safe when the HTML string comes from your own code with no user input mixed in. Static template strings, server-generated markup you trust, and strings you have sanitized are all safe. Never pass raw user input directly to innerHTML.

What should I use instead of innerHTML for inserting text?

Use textContent for plain text. It treats everything as text, so even if a user types HTML tags, they appear as literal characters. Use createElement and appendChild for building new elements programmatically without any string parsing.

Conclusion

innerHTML is the right tool when you need to insert trusted HTML markup into a page. It is fast, readable, and convenient. But it is also the most dangerous DOM method when used carelessly. Never pass user input to innerHTML without sanitization. For plain text, use textContent. For building elements, use createElement. Reserve innerHTML for static HTML strings you control.