HTML entity encoder and decoder
Escape reserved characters so they display instead of being read as markup — or decode entities back into readable text.
The five that matter
HTML reserves a handful of characters. Put one in your text unescaped and the browser reads
it as markup, which is how a stray < swallows the rest of your paragraph — and how a stray
<script> in user input becomes a cross-site scripting hole.
| Character | Named | Numeric | Escape it when |
|---|---|---|---|
| & | & | & | Always — it starts every other entity |
| < | < | < | Always |
| > | > | > | Always, by convention |
| " | " | " | Inside double-quoted attribute values |
| ' | ' | ' | Inside single-quoted attribute values |
Escape the ampersand first when you do this by hand. Escape it last and you will turn every
< you just created into &lt;.
When you actually need this
Showing code on a page. To display <div> as text rather than create a
div, it has to be written <div>. Every code example on this site is escaped this
way.
Putting text into an attribute. A product name containing a quotation mark will end the attribute early and produce markup you did not write.
CDATA-free XML and RSS. Feed descriptions carrying HTML must either be escaped or wrapped in CDATA. Escaping is the more portable of the two.
CSV and spreadsheet exports. Text pulled out of a CMS often still contains entities.
Decoding turns Café back into Café before the file reaches anyone.
Reading scraped markup. Decoding makes a page's text readable again after the HTML has been stripped.
Named or numeric
Both do the same thing. — and — both produce an em dash.
Named entities are readable — you can tell what £ is without
looking it up. There are over 2,000 of them in the HTML5 specification, and this tool uses the several dozen
that come up in practice.
Numeric references work for every character that exists, including ones with no name, and they are valid in XML where most named entities are not. XML defines only five named entities; use anything else in an XML document and the parser will reject it.
The one you almost certainly do not want
is a non-breaking space. It looks like a space, but no line break can happen at it,
and consecutive ones are not collapsed. It has two legitimate uses: keeping a number with its unit
(50 kg) and keeping a name together (Mr Smith).
Everywhere else it is a symptom. WYSIWYG editors emit runs of them for indentation, which produces text that will not wrap on a phone and cannot be reflowed. If you are pasting content in and find dozens, the code cleaner is the page you want — decoding them to plain spaces here first makes them easy to find.
Escaping is not the same as sanitising
Escaping is the right defence when you are inserting untrusted text as content — it guarantees the text will be displayed rather than executed.
It is not enough when the text goes into a URL attribute, an inline style, or a
<script> block. Each of those has its own escaping rules, and a
javascript: URL survives HTML escaping perfectly intact. If you are accepting rich HTML from
users, use a sanitiser with an allow-list — DOMPurify on the client, or the equivalent in your server
language — rather than escaping by hand.
Doing it in code
const escapeHtml = s => s.replace(/[&<>"']/g, c => ({{
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
}}[c]));
// safest of all: let the DOM do it
element.textContent = untrustedString;
import html
html.escape('<a href="x">&</a>') # <a href="x">&</a>
html.unescape("Café") # Café
In a template language you rarely need any of this: Jinja, Twig, Liquid, Blade, ERB and React all escape
interpolated values by default. The bugs happen at the point where someone reaches for
innerHTML, dangerouslySetInnerHTML or a "raw" filter to get around it.
Questions about this tool
Should I escape every accented character?
No. Any page served as UTF-8 — which is every page you should be writing — handles é, 日 and 🎨 natively. The Escape all non-ASCII option exists for legacy systems that mangle anything outside ASCII, not as a default.
Why is ' sometimes a problem?
It is valid in HTML5 and in XML, but not in HTML 4, and Internet Explorer 8 rendered it as literal text. If you need to support anything that old, use ' instead — which is what this tool emits when named entities are turned off.
Can I decode entities with a textarea trick instead?
Setting innerHTML on a detached element and reading back textContent does decode entities, and it is a well-known trick. It also parses the string as HTML, which means untrusted input can trigger side effects. This tool decodes with an explicit table instead, which has no such surface.
What happens to entities the tool does not know?
They are left exactly as they are and listed underneath, so nothing is silently corrupted. All numeric references decode regardless, since those need no lookup table.
Related tools
HTML Formatter
Indent and structure any HTML file.
OpenHTML Beautifier
Turn minified HTML back into readable code.
OpenHTML Pretty Printer
Pretty-print HTML with the indent you want.
OpenHTML Minifier
Strip whitespace and comments from HTML.
OpenHTML Validator
Find unclosed tags and broken nesting.
OpenHTML Code Cleaner
Tidy exports from CMS and email builders.
OpenLearn the why, not just the how
Longer reading on formatting, indentation and minification.