HTML entity encode/decode · Guide

Decode HTML Entities JavaScript: Methods That Actually Work

JavaScript has no built-in decodeHTML function, so every solution borrows the browser's own parser. Which borrowing technique you pick decides whether you have a utility or a cross-site scripting hole.

The safe one-liner: textarea

JavaScript
function decodeEntities(str) {
  const el = document.createElement('textarea');
  el.innerHTML = str;
  return el.value;
}

This works because textarea content is parsed as text: entities are resolved, but tags inside it are never executed or turned into elements. Reading .value gives the decoded string.

The critical detail is textarea specifically. The same code with a div would parse the input as markup, and an injected <img src=x onerror=alert(1)> would fire immediately.

DOMParser for larger input

JavaScript
const doc = new DOMParser().parseFromString(str, 'text/html');
return doc.documentElement.textContent;

DOMParser builds the document in an inert context — scripts do not run and images do not load — so it is safe for untrusted strings and handles very long input more predictably than the textarea approach.

It also decodes numeric references (&#8594;) and hex references (&#x2192;) alongside named ones, which hand-written replace chains routinely miss.

Why not innerHTML

element.innerHTML = untrustedString is the pattern behind a large share of DOM-based XSS. It parses the string as HTML and activates any event handlers it contains:

JavaScript
// dangerous — the handler fires immediately
el.innerHTML = '<img src=x onerror=alert(1)>';

// safe — decode first, then assign as text
el.textContent = decodeEntities(untrustedString);

textContent never interprets markup, so the value is displayed exactly as it reads. A manual replace chain for &amp; &lt; &gt; &quot; is also a bad idea: it misses thousands of named entities and gets the ordering wrong, decoding &amp;lt; into < when it should stay as the literal text &lt;.

Node.js and server-side

There is no DOM in Node by default. Use the he package (he.decode) or entities — both are small, complete, and handle numeric and named references correctly.

If you already have jsdom or linkedom for other reasons, DOMParser is available through them and the browser code works unchanged.

Whatever you use, decode once at the point of display, not repeatedly through your pipeline. Double-decoding turns &amp;lt;script&gt; into an executable tag — the classic filter-bypass bug.

Frequently asked questions

Is the textarea trick safe for untrusted input?

Yes, because textarea parses its content as raw text rather than markup. The same code with a div or span is not safe.

How do I decode numeric entities like &#8594;?

The textarea and DOMParser methods handle numeric and hex references automatically. Manual replace chains generally do not.

What is the Node.js equivalent?

Use the he or entities package. Node has no DOM, so the browser techniques need jsdom or linkedom to work.

Ready to try it?

Open the free browser-based HTML entity encode/decode and apply what you just read — no sign-up, runs locally.

Open the HTML entity encode/decode tool