Encode and decode HTML character references against the full WHATWG table of 2,231 names, with the numeric remapping and the context rules a browser applies.
Initializing in your browser…
Write SEO, Open Graph and X card tags with every value escaped for an attribute, checked against the protocol, and the cut shown per platform.
Generate passwords from the Web Crypto generator by rejection sampling, with the entropy stated exactly from the alphabet, the cost of every rule computed, and crack times against four named attacker models
Create favicons from text, emojis, or images with all device sizes (16x16 to 512x512), background/text color customization, border radius control, and batch download
A log line arrives with HTML character references in it and you need to know what it actually said, including whether somebody escaped it once or twice.
The line as it arrived
<p>Hello</p> then &lt;escaped&gt; then € then 😀
Decoded once, with every reference accounted for
<p>Hello</p> then <escaped> then € then 😀 < at 0 < U+003C > at 5 > U+003E < at 15 < U+003C > at 21 > U+003E & at 31 & U+0026 & at 46 & U+0026 € at 62 € U+20AC The standard remaps this one: € is U+20AC (€), not the C1 control at U+80. These references were written against windows-1252. 😀 at 73 😀 U+1F600
Three things in that output are the ones tools get wrong.
`&lt;escaped&gt;` comes back as `<escaped>`, not as a live tag. That input is the correct encoding of the literal text `<escaped>`, so decoding it once has to return exactly that. A decoder that replaces `&` with an ampersand and then keeps replacing produces `<escaped>` instead, which is escaped markup turned back into live markup. In a tool people paste untrusted text into, that is not a formatting slip; it is the escaping being undone for an attacker.
`€` is the euro sign. The HTML Standard remaps 27 numeric references between 128 and 159, because they were written against windows-1252 rather than Unicode, so the character at U+0080 is not what a browser produces here. Returning the literal code point gives an invisible C1 control and disagrees with every browser.
`😀` is one code point above the Basic Multilingual Plane. Decoding it with `String.fromCharCode` yields a lone surrogate rather than the emoji, and encoding the emoji by walking UTF-16 code units yields `�`, the first half of a surrogate pair, which a browser reads back as a replacement character. A flag emoji, which is two astral code points, comes out as that same broken half twice and loses a character outright.
Every entry is listed with its position and code points because a decoded value is often invisible: the ` ` this tool reports as U+00A0 is not the U+0020 that looks identical beside it. All 2,231 named references are checked against the browser’s own HTML parser in the project tests, which is the only reference that decides what a character reference means.
Encode text so it cannot be read as markup, or decode character references the way a browser decodes them. The full WHATWG table of 2,231 names, the numeric remapping the standard specifies, emoji handled as code points, and the attribute-context rule that decides whether <code>&lt</code> without a semicolon is a reference at all.
A character reference is how a character that would otherwise be read as markup gets written as text. This tool carries the whole table the HTML Standard defines: 2,231 named references, of which 2,125 end in a semicolon and 106 do not. Every one of them is checked, in the project tests, against the browser's own HTML parser, which is the definition of what a reference means.
Decoding runs exactly once, and that is the part worth understanding. The text &amp;lt; is the correct encoding of the literal characters &lt;, and it must come back as &lt;, not as a live less-than sign. A decoder that replaces &amp; with an ampersand and then keeps replacing turns escaped markup into live markup, which in a tool people paste untrusted text into is how a payload gets assembled rather than defused. When the result still contains references, the page says so and explains why that is correct rather than quietly running again.
Numeric references are resolved the way the standard resolves them, which is not simply "the number you wrote". Twenty-seven values between 128 and 159 are remapped, because those references were written against windows-1252 rather than Unicode: &#128; is the euro sign, not the invisible C1 control at U+0080, and &#153; is a trade mark sign. A null reference, a surrogate, and anything above U+10FFFF all become U+FFFD, since none of them is a character. Each of those is named on screen with the reason, rather than silently producing something invisible.
Encoding walks code points, not UTF-16 code units, which is what makes emoji work. An emoji is one character above the Basic Multilingual Plane, and its reference is &#128512;. A tool that walks code units emits &#55357;, the first half of a surrogate pair, which is not a character and which a browser reads back as U+FFFD; a flag, which is two astral code points, comes out as the same broken half twice and loses a character outright. Three scopes are offered: only the five characters that can change how markup parses, everything above ASCII, or everything the standard has a name for. The five are always encoded, including both quote characters, because an unescaped double quote ends an attribute value and everything after it becomes new attributes.
Decoding is context dependent and the tool lets you say which context you are in. The 106 references with no semicolon are decoded in element content, so &amp becomes an ampersand, but in an attribute value a browser does NOT decode one that is followed by an equals sign or an alphanumeric. That single rule is what keeps a query string like ?a=1&lt=2 intact instead of turning it into ?a=1<=2, and it is the reason the same text can mean two things depending on where it sits.
Every reference found is listed with its position, the characters it produced and their code points, so a value that looks like nothing (a non-breaking space, a combining mark, a replacement character) can still be identified. A search box covers the whole table by name or by pasting a character. The text being converted stays in the page: it is not written into the address bar, the history, or a share link, though the settings are, so a configuration can still be shared without the content going with it.
Encode only the five characters that can change how markup parses, so the sample shows as written rather than being parsed as tags.
Decode references back to characters, and find out whether what you have was encoded once or twice.
Every reference is listed with its code points, so a non-breaking space, a combining mark or a replacement character can be identified rather than guessed at.
Switch to the attribute context to see whether an unterminated reference such as &lt=2 was the cause.
Because the input was encoded twice, and that result is correct. &amp;lt; is how the literal text &lt; is written safely, so decoding it once must give &lt; back. A decoder that keeps going turns escaped markup into live markup, which is exactly what escaping was meant to prevent.
The HTML Standard remaps 27 numeric references in the 128 to 159 range, because they were written against windows-1252 rather than Unicode. Browsers do this, so a tool that returns the literal code point disagrees with every browser. The tool names each remapping when it applies one.
Yes. Encoding walks code points, so an emoji becomes 😀 and survives a round trip. Walking UTF-16 code units instead produces �, half a surrogate pair, which is not a character and which a browser reads back as a replacement character.
106 of the references have no trailing semicolon. In element content a browser decodes them. In an attribute value it does not, when the reference is followed by an equals sign or an alphanumeric, which is what stops a query string like ?a=1<=2 from being mangled into ?a=1<=2.
It makes text safe to place in HTML, which is one context. A value going into a JavaScript string literal, a URL, or CSS needs that context's own escaping, and none of them is HTML escaping. Escaping for the wrong context is a common way an injection survives.
Because they are different characters. is U+00A0, a non-breaking space; an ordinary space is U+0020 and has no named reference at all. Encoding one as the other changes how the text wraps and what a comparison returns.
This runs as client-side JavaScript. Keys, tokens, payloads, and other inputs never leave your device.