HTML decode

Paste text containing character references and read what it says. Named ones, decimal ones and hexadecimal ones are all handled, and anything unrecognised is left visibly alone rather than quietly dropped — which is what tells you whether the input was odd or the decoder was.

Result

The answer appears here as you type.

  • Where it runs

    Nothing is uploaded, because there is no file — it is worked out in this page.

  • No queue, no account

    It answers as fast as your machine can, and it never asks who you are.

  • As often as you like

    Nothing is counted and nothing is capped — answering again costs us nothing.

How it works

  1. Paste the text into the box.
  2. Read the result. Named, decimal and hexadecimal references are all recognised.
  3. If something comes back untouched, it was not a reference this knows — see below for why that is deliberate.

Why this one refuses to use the browser to do it

The one-line implementation is to set `innerHTML` on a detached element and read `textContent` back. It handles all 2,231 named references for free, it is what nearly every snippet on the internet suggests, and it is an HTML parser being handed untrusted input.

A detached element does not run scripts. It does still fetch: an image tag with a remote source produces a request the moment it is parsed, before anything is read back. A page whose whole claim is that your input never leaves the device cannot contain a path where pasting the wrong string sends one. So the named set here is a lookup table, and that table is what lets this page make the claim it makes.

A finite table, and what falls outside it

The table holds the references that actually turn up: the five structural ones, the typographic set a CMS produces — dashes, curly quotation marks, the ellipsis — currency symbols, the common mathematical operators, the German umlauts and a handful of arrows. Numeric references need no table at all, since they name a code point directly.

Anything else comes back exactly as it went in. That is deliberate and it is the more useful behaviour: an untouched reference is visible, so you can see that a strange one was in your input rather than wondering where a word went. Dropping the unknown would give tidier output and a worse answer.

Double escaping, and fixing it at the right end

If the result still contains references, the text was escaped twice — the doubled form decodes to the single one, and only a second pass gives the character itself. It is the commonest reason to be on this page, and the cause is almost always the same: a value escaped when it was stored and escaped again when it was displayed.

Decode as many times as it takes to read it, then fix it once at the source rather than repeatedly at the destination. Text should be stored as it was written and escaped only when it is inserted into a page; escaping on the way in is how the doubled form got into the database to begin with.

The references this deliberately will not decode

A numeric reference naming a surrogate — the range from U+D800 to U+DFFF — is left alone, as is anything past U+10FFFF. Both are outside the range of real characters, and a decoder that produces one anyway hands back a string that cannot be encoded to UTF-8 at all.

The practical effect of doing it the other way is that the next thing you paste the result into throws, somewhere unrelated, with an error about invalid input that mentions none of this. Leaving the reference visible keeps the problem where the problem is.

Where escaped text usually comes from

RSS and Atom feeds, where an item body is escaped so it can live inside XML. CMS exports, where a title carries a numeric reference because an editor typed a curly apostrophe. Log lines and error messages that passed through a template on the way out. API responses from something that renders HTML internally and hands you the rendering.

In every one of those the escaping was correct where it happened and is wrong where you are now, which makes decoding a step in a pipeline rather than a repair. If you are doing it by hand more than once, the thing to change is whatever is giving you HTML when you asked for text.

HTML decode: common questions

Why has my entity been left as it was?

Because it is not in the table this uses, or because it names something that is not a valid character. Unknown references are left visible on purpose — dropping them would give tidier output and hide the fact that something odd was in the input. If it is a real reference that ought to be recognised, it is worth reporting.

The result still has escaped ampersands in it. Why?

The text was escaped twice, so one pass peeled off one layer. Decode again and you will get the real character. The cause is nearly always a value escaped when it was saved and escaped again when it was rendered, which is worth correcting at the source rather than at every place it is read.

Does this handle numeric references as well as named ones?

Yes — decimal and hexadecimal both, and neither needs a lookup table because each names a code point directly. What is deliberately excluded is any reference to a surrogate or past the last valid code point, since decoding those yields a string that cannot be encoded back to UTF-8.

Is it safe to paste untrusted text here?

Yes, and the design is specifically about that. The usual implementation of an entity decoder feeds the text to an HTML parser, which can issue network requests for images before anything is read back. This one uses a lookup table and never parses, so pasting a hostile string does nothing but decode it.

Can I decode a whole HTML document with this?

You can, but you probably do not want to. Decoding a document turns its escaped examples of markup back into markup, which changes what the document means. This is for a field, a title or a line of text that was escaped once and should not have been.

Other tools