Skip to content

JavaScript Tutorial JavaScript Security Basics

textContent vs innerHTML: Safe Output

Lesson 229 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

Two properties set the content of an element. They look similar, and they differ in one crucial way.

textContentinnerHTML
Treats the value asplain textHTML (parsed)
"<b>hi</b>" shows asthe literal characters <b>hi</b>bold “hi”
Safe with untrusted datayesno

See the difference

index.html
HTML
<p id="a"></p>
<p id="b"></p>
<script>
  const text = "<strong>Hello</strong> <em>world</em>";

  document.querySelector("#a").textContent = text;   // shown as text
  document.querySelector("#b").innerHTML = text;     // parsed as HTML

  console.log(document.querySelector("#a").children.length); // → 0   (no elements created)
  console.log(document.querySelector("#b").children.length); // → 2   (two elements created)
</script>

textContent created no elements. innerHTML created real <strong> and <em> elements. If the string came from a user, those could be elements they chose, including ones that run code.

The rule

> Use textContent for anything that is text. Use innerHTML only with markup you wrote yourself.

script.js
JS
// A visitor's name: text
greeting.textContent = "Hello, " + name;

// NOT: greeting.innerHTML = "Hello, " + name;

Building a list safely

Create elements, set text, append.

index.html
HTML
<ul id="people"></ul>
<script>
  const people = ["Maya", "Arjun", "<img src=x> not an image"];

  for (const person of people) {
    const li = document.createElement("li");
    li.textContent = person;
    document.querySelector("#people").append(li);
  }
</script>

The last entry appears as text. No image is created.

When you need HTML structure with data

Use a <template> and fill in text with textContent:

script.js
JS
const row = template.content.cloneNode(true);
row.querySelector(".name").textContent = user.name;

Escaping as a last resort

If you really must build an HTML string, escape the dynamic parts, replacing &, <, >, " and '.

script.js
JS
function escapeHTML(text) {
  return String(text)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#39;");
}

console.log(escapeHTML("<script>alert(1)</script>"));
// → &lt;script&gt;alert(1)&lt;/script&gt;

Correct escaping depends on where the value goes (element text, attribute, URL, script), and it is easy to get wrong. Prefer DOM methods that avoid the problem.

Other risky spots

These also parse HTML: outerHTML, insertAdjacentHTML, document.write, DOMParser (when you insert the result), and setting srcdoc. Treat them like innerHTML.

If you must allow user HTML

For a rich text comment box, use a sanitiser library (DOMPurify is a well-known one) that parses the HTML and removes anything dangerous, then insert the cleaned result. Keep it updated, and still enforce rules on the server. A newer built-in Sanitizer API is being developed. Check its support before relying on it.

Performance note

textContent is also faster, because the browser does not need to parse anything.

dangerous
Never trust "it is only my own data"
Data from an API, a database, a URL or storage can contain something you did not expect. Treat all of it as text unless you have a clear reason not to.