Most DOM manipulation doesn't need innerHTML at all — textContent, createElement, and modern APIs like setHTML() let you build and update the DOM safely by default, reserving raw HTML parsing for the rare cases that genuinely need it.
1Safe DOM Manipulation | JavaScript Tutorial - In-Depth Guide Part 1
textContent sets an element's text as plain text, guaranteeing whatever you assign is never parsed as HTML — the safest default for displaying any string that isn't meant to contain markup.
el.textContent = '<script>alert(1)</script>';
// Displays the LITERAL text "<script>alert(1)</script>" on the page, does not executetextContent Is Always Safe
2Safe DOM Manipulation | JavaScript Tutorial - In-Depth Guide Part 2
Building elements programmatically with createElement, setting properties directly, and using appendChild avoids string-based HTML parsing entirely, sidestepping injection risk by construction.
function renderComment(comment) {
const div = document.createElement('div');
div.className = 'comment';
const strong = document.createElement('strong');
strong.textContent = comment.author; // safe
div.appendChild(strong);
div.appendChild(document.createTextNode(comment.text)); // safe
return div;
}Building Elements Programmatically
3Safe DOM Manipulation | JavaScript Tutorial - In-Depth Guide Part 3
Setting attribute VALUES safely also matters — even without innerHTML, an attacker-controlled string used as an href or src attribute can trigger a 'javascript:' URL injection.
function isSafeUrl(url) {
try {
const parsed = new URL(url, location.origin);
return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
} catch {
return false;
}
}
if (isSafeUrl(userProvidedUrl)) link.href = userProvidedUrl;Validating Attribute Values
4Safe DOM Manipulation | JavaScript Tutorial - In-Depth Guide Part 4
The newer, standards-track 'setHTML()' method (on Element, behind growing browser support) parses a string as HTML but automatically sanitizes it against a safe, built-in default configuration.
// Where supported, this parses AND sanitizes in one step:
el.setHTML(userProvidedHtml); // strips dangerous elements/attributes automaticallyThe setHTML() API
5Safe DOM Manipulation | JavaScript Tutorial - In-Depth Guide Part 5
A practical rule of thumb: reach for textContent by default, createElement/appendChild when building structured markup from data, and a trusted sanitizer (or setHTML) only when you genuinely need to render externally-sourced HTML.
// Decision order:
// 1. textContent — for plain text (default choice)
// 2. createElement/appendChild — for structured markup from data
// 3. sanitizer / setHTML() — only for genuine rich HTML contentA Practical Decision Hierarchy
6Step-by-Step Breakdown
textContent sets an element's text as plain text, guaranteeing whatever you assign is never parsed as HTML — the safest default for displaying any string that isn't meant to contain markup.
Checkpoint: If you set el.textContent to a string containing <script>, does that script execute?
- →Yes, textContent still parses HTML
- →No, it displays as literal, harmless text
Building elements programmatically with createElement, setting properties directly, and using appendChild avoids string-based HTML parsing entirely, sidestepping injection risk by construction.
Setting attribute VALUES safely also matters — even without innerHTML, an attacker-controlled string used as an href or src attribute can trigger a 'javascript:' URL injection.
Checkpoint: Can setting an untrusted string as an element's href attribute be dangerous, even without ever using innerHTML?
- →Yes, a javascript: URL can execute when clicked
- →No, attribute values are always inherently safe
The newer, standards-track 'setHTML()' method (on Element, behind growing browser support) parses a string as HTML but automatically sanitizes it against a safe, built-in default configuration.
A practical rule of thumb: reach for textContent by default, createElement/appendChild when building structured markup from data, and a trusted sanitizer (or setHTML) only when you genuinely need to render externally-sourced HTML.
Next, we'll explore 'HTML Sanitization'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Programmatic DOM Construction Makes It Easier to Guarantee Correct Semantic Structure
Building elements with createElement lets you deliberately set the correct tag names, ARIA attributes, and nesting for accessibility at each step, compared to an innerHTML template string where a subtle typo in the markup can silently produce invalid or inaccessible structure.
SEO Implications
- 1
Safe DOM Construction Prevents Content-Corrupting Injection Attacks
Ensuring rendered content cannot be hijacked via injection helps preserve the integrity and trustworthiness of indexed pages, which matters both for user trust and for avoiding search engine security flags.
Best Practices
Default to textContent for Any Plain-Text Content
It's immune to injection by construction and requires no additional validation or sanitization step for the common case of displaying non-HTML text.
Validate URL Protocols Before Setting href/src Attributes from Untrusted Data
Even without innerHTML, an attacker-controlled javascript: URL set as a link's href is a real, exploitable injection vector triggered on click.
Frequent Bugs
Building a list of items with an innerHTML template string purely out of habit, even though the content is simple plain text that createElement/textContent could handle just as easily and more safely.
Default to textContent and createElement for straightforward text/structure; reserve innerHTML (with sanitization) only for genuine rich-HTML needs.
Allowing a user-provided URL to be set directly as a link's href without validating its protocol, enabling a javascript: URL injection when the link is clicked.
Parse the URL and explicitly check that its protocol is in an allow-list (http:, https:, mailto:) before assigning it to an href or src attribute.
Real-World Examples
Safely Rendering a List of User Comments
A comment feed needed to display each comment's author name and text without any risk of embedded HTML/script executing.
function renderComments(comments) {
const list = document.createElement('ul');
comments.forEach((c) => {
const li = document.createElement('li');
const author = document.createElement('strong');
author.textContent = c.author;
li.append(author, ': ', c.text); // all safely treated as text
list.appendChild(li);
});
return list;
}