What HTML encoding is for
HTML has exactly one mechanism for distinguishing markup from content, and it is punctuation. A
< starts a tag and an & starts a character reference. That works fine
until the text you want to display contains those characters — a code sample, a maths expression,
a company name with an ampersand in it, or a user comment. HTML encoding replaces each
such character with an entity reference: <, >,
&, ", '. The parser sees the reference,
converts it back to the character, and puts it in the document as text rather than acting on it.
Decoding is the reverse trip, and you need it more often than you would expect. Data that has passed
through a CMS, an RSS feed, a scraped page or an over-eager template layer frequently arrives with
&amp; where a single ampersand belongs, or with an entire JSON string escaped as
though it were going into a web page. Decoding once, or several times, gets the original text back so you
can see what you are actually dealing with.
The five characters that matter
| Character | Named entity | Numeric | Escape it when |
|---|---|---|---|
& | & | & | Always — otherwise it may start a reference |
< | < | < | Always — otherwise it may start a tag |
> | > | > | By convention, to avoid ambiguity |
" | " | " | Inside a double-quoted attribute value |
' | ' | ' | Inside a single-quoted attribute value |
One footnote on ': it is defined in XML and in HTML5, but not in HTML 4, so very old
parsers may not recognise it. The numeric form ' is understood everywhere, which is
why this encoder emits the numeric reference for an apostrophe by default and why most server-side
escaping libraries do the same.
Encoding and security: what it does and does not fix
Contextual output encoding is the primary defence against cross-site scripting, and HTML entity encoding
is the version of it that applies to one specific context: text content and quoted attribute values. Get
it right there and a payload like <script>alert(1)</script> becomes an inert
string that the user sees rather than code that the browser runs.
The failure mode is applying it in the wrong place. Entity encoding does nothing useful if the value lands:
- Inside a
<script>block. The HTML parser does not decode entities in script content, so"stays as literal characters and the injection still works. JavaScript string escaping is a different job with different rules. - In an unquoted attribute. Without quotes, a space is enough to start a new attribute,
so an attacker can add
onmouseover=…without ever using a bracket or a quote. - In a URL context.
href="javascript:alert(1)"contains no character that entity encoding touches. URL attributes need a scheme allowlist. Percent-encoding, which the URL encoder handles, is the escaping scheme for the query-string part of a URL — a completely separate concern from HTML entities. - Inside CSS. Style contexts have their own escaping rules and their own historic injection tricks.
The other classic mistake is escaping too early. If you HTML-encode data on the way into your database, every non-HTML consumer of that data — a JSON API, a CSV export, a mobile client, an email — receives text full of entities it will render literally. Store the raw value; escape at render time, in the template, for the context you are rendering into. When you see entities in an API response body while testing with the API tester, that is almost always the symptom you are looking at.
Worth noting explicitly: this page never uses innerHTML. A very common way to write an entity
decoder is to assign the input to a throwaway element's innerHTML and read back its
textContent, which is convenient and genuinely dangerous — it can execute event handlers and
trigger network requests from image tags in the process. The decoder here walks the string and resolves
references from an explicit table instead, and the output is written with textContent.
Using the encoder
Three encoding scopes
Special characters only escapes the five characters in the table above and leaves everything else alone. This is what a server-side escaping function does and what you want almost all of the time; it keeps accented letters, emoji and CJK text readable in the source. Special + common named additionally converts a handful of characters that are easy to lose in editing — the non-breaking space, en and em dashes, curly quotes, the copyright and trademark signs — into their named references, which makes an invisible non-breaking space visible in your source. All non-ASCII converts every character above U+007F into a decimal numeric reference, producing pure ASCII output. That last mode is occasionally necessary for systems that mangle UTF-8, but it bloats the output and hurts readability, so use it only when something downstream forces your hand.
Decoding
The decoder handles named references from a built-in table covering the entities you will realistically
meet, plus every decimal (—) and hexadecimal (—) reference,
including code points outside the Basic Multilingual Plane, which are reassembled into the correct
surrogate pair. Unrecognised references are left untouched rather than silently dropped, so
¬arealentity; comes back exactly as you typed it — if a decode leaves something behind
you can see it instead of losing data.
Double encoding
If your text shows &lt;, it was encoded twice. Decode once and you get
<; decode again and you get <. Use the Output → input
button to feed the result back in and peel the layers one at a time. Double encoding is nearly always a
pipeline bug — two components each doing the escaping the other already did — and counting the layers
tells you how many places to look.
Encoding is not compression, encryption or hashing
Entity encoding changes representation only; the information is identical and anyone can reverse it. If you need to prove a payload was not tampered with, compute a digest with the hash generator. If a value looks like opaque base-64 rather than escaped text, the Base64 decoder is the tool you want. And if what you are staring at is a badly escaped structured document rather than prose, the XML formatter or the JSON formatter will usually make the real shape obvious.
Frequently asked questions
Do I need to escape the greater-than sign?
Strictly, no — a lone > in text content is legal. It is escaped by convention because it
costs nothing and removes any ambiguity for tools that scan markup with regular expressions.
Why does my page show &nbsp; as visible text?
Something escaped an already-escaped string. The ampersand of was turned into
&, so the browser now renders the entity name rather than a space. Decode once.
Is HTML encoding enough to stop XSS?
Only for HTML text and quoted attributes. Script blocks, URL attributes, unquoted attributes and CSS all need their own escaping. Match the escaping to the context, and prefer a template engine that does it for you automatically.
Does this tool use innerHTML to decode?
No, deliberately. The decoder resolves references from an explicit table and the output is written with
textContent, so nothing you paste can execute or trigger a request.
What happens to characters like emoji?
In the default mode they pass through unchanged, which is correct for any UTF-8 page. In the "all non-ASCII" mode they become numeric references built from the real code point, and decoding restores the original character exactly.
Related tools and reference
API Tester
Inspect a response body and see whether the server escaped it before sending.
URL Encoder
Percent-encoding for query strings — a different scheme for a different context.
XML Formatter
XML shares the same five escapes; format a document to see where they belong.
Hash Generator
Confirm that a round-trip through encoding really did return the same bytes.
HTTP Headers
Content-Type and charset decide how a browser interprets your escaped output.
400 Bad Request
What an API returns when escaped input arrives where raw input was expected.