What decoding entities means

Decoding resolves the three entity notations back into characters: named (&auml;), decimal (&#228;) and hexadecimal (&#xE4;) all become ä. HTML5 defines about 2,200 names, which is why a hand-written lookup table always misses something. This tool hands each matched token to the browser's own entity parser through a detached <textarea>, so the table is exactly the one your browser uses for real pages, including the legacy forms that work without a semicolon.

Only the matched tokens are decoded, never the whole input as markup. A pasted <script> stays the literal text <script> in the output pane, which is what you want from a debugging tool.

How to use this decoder

Paste on the left, read the plain text on the right. One decoding round runs by default, which is what you need to check whether your output chain escapes the right number of times.

  1. Paste the escaped text. A database field, a scraped page fragment, an API response, an .html file dropped on the pane.
  2. Read the entities count under the panes: it tells you how many references were resolved in this round.
  3. Turn on --repeat if the output still contains entities, then copy or download the result.

--repeat

Decodes repeatedly until nothing changes, up to six rounds. Use it to read double- or triple-escaped content in one step. Leave it off when you are diagnosing an escaping bug, because the number of rounds needed is the diagnosis: two rounds means exactly one escaping layer too many.

Double-escaped text

The most common reason people land on an entity decoder is text that renders as &auml; on a live page. The mechanism is always the same: the value was escaped once by application code, then again by an auto-escaping template engine.

Escaping roundsStored / transmittedRenders as
0Müller & SöhneMüller & Söhne
1M&uuml;ller &amp; S&ouml;hneMüller & Söhne
2M&amp;uuml;ller &amp;amp; S&amp;ouml;hneM&uuml;ller &amp; S&ouml;hne

Row two is correct and invisible to the visitor; row three is the bug. The giveaway is &amp; in front of another entity name. Fix the producer and leave the stored data escaped exactly once, rather than running a bulk decode over the database. A bulk decode also unescapes the values that were meant to contain literal < characters, and that damage is hard to reverse.

The notations you will meet

InputDecodes toNote
&amp;&Must be decoded last when done by hand
&lt; &gt;< >The two that carry injection risk
&#39; &apos;'&apos; is HTML5 and XML, not HTML4
&nbsp;U+00A0Invisible, does not behave like a normal space
&#8217;Typographic apostrophe, breaks exact-match comparisons
&#8211; &#8212;– —En dash and em dash, from word processors
&#x1F600;😀Astral plane; naive decoders emit broken surrogates
&copy (no semicolon)©Legacy HTML5 tolerance, invalid in XML
&shy;U+00ADSoft hyphen, invisible until the line wraps

The last group is why decoding is worth doing even when the text already looks fine: several of these characters are invisible or nearly identical to an ASCII lookalike, and they are exactly what breaks a string comparison, a slug generator or a CSV import three steps later.

Why decoded HTML is not safe to render

Decoding is the inverse of the escaping that protects a page, so decoded text is by definition unsafe as markup. &lt;script> is inert; <script> is not. Three rules follow, and they are the ones we would enforce in review:

  • Never store decoded HTML and render it unescaped. If you decode for processing (search indexing, text analysis, an export), escape again at output; OWASP's XSS prevention cheat sheet lists which escaping each context needs.
  • Never decode as a way to fix double-escaped display. Remove the extra escaping step in the pipeline instead; decoding the stored data leaves the pipeline broken and now the data is inconsistent as well.
  • Do not use a decoded value in a security decision. Filters that inspect decoded input while the application decodes again are the classic double-decoding bypass.

When you do need to escape again, the entity encoder has a minimal mode that touches only the five security-relevant characters, which is the right level for a UTF-8 page.

Where escaped text comes from

A few recurring sources, since knowing the origin usually tells you what else to clean up. RSS and Atom feeds escape entire HTML fragments inside <description>, so a feed item often needs one decoding round before it is readable. Scraped pages carry whatever the site emitted, frequently a mix of literal UTF-8 and entities in the same document. Old CMS databases (WordPress, TYPO3, Joomla) store editor output with &nbsp; and curly quotes throughout. API responses that carry HTML inside JSON are escaped twice by definition, once for JSON and once for HTML, and the JSON layer is unescaped by your parser while the HTML layer is not.

For that last case the order matters: parse the JSON first, then decode the entities in the resulting string. Doing it the other way round corrupts the JSON structure, because a decoded quote character will close a string early.

Entities that refuse to go away

How do I clean up HTML entities stored in a WordPress or MySQL database?

Decode them once, then stop the layer that put them there. Content saved through an old editor, an importer or a form with PHP magic quotes ends up with &amp;#039; and &amp;amp; sitting in the table itself, so every page renders the entity as visible text no matter what the template does. The repair is a one-off UPDATE with REPLACE over the affected column (take a dump first), or in WordPress the search-and-replace step of a migration plugin. Before you run it, find out which code path escapes on save, because a cleaned column refills within days if the writer is still escaping. Decode a few sample rows here first to see how many layers you are dealing with.

How do I strip HTML tags and entities from text?

Remove the tags with a parser, then decode the entities, in that order. Reversing the two lets an encoded &lt;script&gt; turn into a real tag after the tag removal has already run, which is a classic filter bypass. In Python: BeautifulSoup(html, "html.parser").get_text() handles both steps correctly. In PHP: html_entity_decode(strip_tags($s), ENT_QUOTES, "UTF-8"). In JavaScript, parse with DOMParser and read textContent instead of writing your own regex, because a regex over tags fails on attributes containing angle brackets, on comments and on unclosed elements. Expect to normalise whitespace afterwards, since block tags leave nothing where a line break used to be.

What does &amp;auml; mean?

It is double-escaped text: &amp; is itself the entity for &, so one decoding round produces &auml; and only the second round produces ä. It happens when text passes through two escaping layers, typically a template engine that auto-escapes plus application code that escaped the value already. Turn on --repeat here to see the final text, but treat it as a bug report rather than a fix, because the extra layer is corrupting other characters too.

How do I decode HTML entities in JavaScript?

The reliable one-liner uses the browser's own parser through a detached element: const ta = document.createElement("textarea"); ta.innerHTML = str; return ta.value. A textarea is important, because its content model is plain text, so tags in the input cannot become elements. Never use innerHTML on a div for this, and never use eval-style tricks. On the server, use a library (he in Node.js, html.unescape in Python) rather than a regex over a hand-written table.

Why does my page show &auml; as literal text instead of ä?

Because the ampersand was escaped a second time, so the browser sees &amp;auml; and correctly renders the text &auml;. The cause is nearly always double escaping in the output chain: a template that auto-escapes receiving a value that was already escaped. The fix is to remove one of the two escaping steps, not to decode the text before storing it, which just moves the problem into the database.

Do HTML entities need a trailing semicolon?

They should have one, and in XML they must. HTML5 tolerates a set of legacy entities without it, so &copy renders as © in a browser, which is why you sometimes see a copyright symbol appear in the middle of a URL containing "copy". That tolerance is limited to about a hundred historical names; a numeric reference without a semicolon is a parse error in some contexts. Always write the semicolon.

What is the difference between HTML entities and URL encoding?

Different alphabets for different destinations. HTML entities (&amp;, &#233;) protect characters from the HTML parser and appear in markup. Percent encoding (%26, %C3%A9) protects characters from the URL parser and appears in addresses. They are not interchangeable, and a value that travels through both needs both, applied in the right order: percent-encode for the URL, then HTML-escape the resulting attribute value.

Can decoded entities contain malicious code?

The decoded text can absolutely contain <script> or an onerror attribute, because decoding is exactly the step that turns harmless text back into markup. This page never renders it, but your application must not either. If you decode entities server-side and then write the result into a page, you have removed your own protection. Decode for reading and processing; escape again at output.

Why do I see &#8217; instead of an apostrophe?

&#8217; is the right single quotation mark (U+2019), the typographic apostrophe that word processors and CMS editors substitute automatically. It is a different character from the ASCII apostrophe ', and code that compares strings or matches on ' will not find it. When cleaning imported text, decoding the entity is only half the job: you often also want to normalise the curly quotes to straight ones before the data hits a search index.

What is &nbsp; and why is it everywhere in pasted content?

&nbsp; is a non-breaking space (U+00A0). WYSIWYG editors emit it to preserve consecutive spaces and to prevent line breaks, so pasted content from Word or an older CMS is often full of them. After decoding here they become invisible U+00A0 characters that look like normal spaces but do not match a \s+ split in some regex flavours and break trimming. When cleaning such text, replace them explicitly rather than trusting a visual check.