Turn HTML entities back into plain characters. Use it
when a CMS export, a scraped page or an RSS feed arrives
full of & and
’ where the punctuation should be.
<p class="note">Café & croissant — €4.50</p> <p class="note">Café & croissant — €4.50</p> The full HTML5 set, so é, …, and the other 2,000-plus names all resolve.
Decimal like é and hexadecimal like 😀, including emoji and other characters outside the Basic Multilingual Plane.
& without the semicolon is decoded the way a browser would. Fix it in the source anyway, since stricter parsers reject it.
Tick Decode twice to unwrap &lt; in one pass. If that fixes your text, look for a template that escapes a value twice. That is the bug to fix.
Decoding <script> gives you the literal characters <script> as text. The result goes into a textarea and is never inserted into this page, so pasting hostile markup here is safe. Keep the same distinction in your own code: turning an entity back into a character does not make it safe to put in the DOM.
// In a browser, borrow the parser's own entity table
function decodeEntities(str) {
const el = document.createElement("textarea");
el.innerHTML = str;
return el.value;
}
console.log(decodeEntities("Café & croissant — €4.50"));
// Café & croissant — €4.50
// A textarea, not a div: a div would parse tags as markup.
// In Node.js there is no DOM, so use a library:
// npm i he
// require("he").decode(str); Using innerHTML on a div and reading textContent works too, but it parses any tags in the input as elements. A textarea treats its content as text, which is what you want here.
<?php
$encoded = 'Café & croissant — €4.50';
// Decodes every named and numeric entity
echo html_entity_decode($encoded, ENT_QUOTES | ENT_HTML5, 'UTF-8'), PHP_EOL;
// Only the five special ones, the inverse of htmlspecialchars
echo htmlspecialchars_decode('<b>bold</b>', ENT_QUOTES), PHP_EOL;
// Double-encoded input needs two passes
$twice = html_entity_decode(html_entity_decode($encoded, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); import html
encoded = "Café & croissant — €4.50"
print(html.unescape(encoded))
# Café & croissant — €4.50
# html.unescape handles the full HTML5 named set plus numeric
# references, including ones missing their semicolon.
# Scraping? BeautifulSoup already decodes entities while parsing,
# so decoding again gives you double-decoded text. Entities show up in scraped pages, RSS and Atom feeds, CMS exports and any API that returns HTML fragments. Decode once at the boundary where the data arrives, store the plain text, then escape again when you output it. Stored escaped text is what produces the &amp; that later turns up in a CSV export.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
All of them: the full HTML5 named set, decimal references like é and hex ones like 😀.
No. It only converts entity references back to characters. <b> becomes the text <b>, not bold text.
The text was encoded twice upstream, usually by a template that escapes a value the framework already escaped. Tick Decode twice here to confirm it, then fix the template rather than the data.
Yes, for the common ones. Browsers accept & without the semicolon and so does this decoder. Fix it at the source anyway, since stricter parsers such as XML reject it.
Yes. The result is shown as text in a textarea, never inserted into the page, so nothing in it can run.
The HTML encoder escapes text for safe output. If the text is percent-encoded rather than entity-encoded, you want the URL decoder; if it is Base64, the Base64 decoder.