Around 2021, a WP Adminify support ticket landed with a screenshot that still makes me wince. A user had set a custom admin footer text — a perfectly innocent copyright line with a © and a link to their agency. On their screen it rendered as © 2021 — Bright & Co. Three visible entities, zero rendered characters. The culprit was me. My save routine escaped the text, my render routine escaped it again, and somewhere in between a filter escaped it a third time. By the time it hit the browser, that poor ampersand had been escaped four times over. I counted.
The fix took ten minutes. Finding it took two evenings, because escaped text looks almost right. You skim © in a database dump and your brain autocorrects it to ©. I ended up pasting strings into a scratch HTML file over and over just to see what the browser would actually render at each layer. That's a miserable workflow, and it's exactly why an HTML entity encoder decoder is one of the first tools I built into toolz.dev.
The flip side of the same coin is scarier. A few months before that ticket, during a code review of my own plugin, I found a settings field that echoed user input into an admin notice without esc_html(). Anyone with access to that field could have stored <script> and had it execute for every admin who loaded the page. Stored XSS, in my own code, one missing function call away. Nobody exploited it — I got lucky. But it permanently changed how I think about escaping: it's not a formatting chore, it's the boundary between "text" and "code."
So this guide covers both directions. Encoding, so untrusted text stays text. Decoding, so you can read what some over-eager pipeline mangled. And enough theory — named vs numeric references, the five characters that actually matter, why order of operations causes double-escaping — that you can debug this stuff instead of guessing.
TL;DR: Paste your text into the toolz.dev HTML Entities Encoder/Decoder to convert between raw characters and entities in either direction — named, decimal, or hex. It runs 100% client-side, so user content and PII never leave your browser. Rule of thumb: always escape the five specials (
& < > " ') in untrusted input, and encode&first or you'll double-escape.
Key Features
Encode and Decode in Both Directions
Half the time I need to turn <script> into <script> so it displays as text in a blog post. The other half I'm going the opposite way — turning a scraped &#8217;s back into a readable apostrophe. The tool handles both. Paste text, pick encode or decode, done. No mode-hunting, no separate tools for each direction. That sounds trivial until you've used tools that only decode, and you find yourself opening a second tab to encode a code sample for your docs. Round-tripping is also a great sanity check: encode, decode, and confirm you get your original string back. If you don't, something in your input was already partially escaped — which is itself useful information.
Named Entities: &, <, © and Friends
Named character references are the human-readable ones — & for &, < for <, © for ©, — for an em-dash. The tool carries a curated table of 147 names, not just the famous five: typography ( , …, ’, “), currency, math and arrows, Greek letters, the full Latin-1 accented range, and a few odds like card suits. That range is what real-world content actually needs — WordPress emits , …, and ’ through wptexturize constantly, and a decoder that only knows a dozen names leaves half your text littered with unresolved references.
Be clear on what 147 is not: the WHATWG HTML Standard defines over 2,200 named references, so this is a practical subset rather than the complete table. If a name isn't in it, decoding leaves the reference untouched rather than guessing — &bogus; comes out as &bogus;. Encoding has the complementary behavior, and it's the more useful half: any character without a name in the table falls back to a decimal numeric reference automatically, so nothing is ever silently dropped. An emoji encodes as 🌍, Chinese text as 你好. Names where they exist, numbers everywhere else.
One more divergence from browser behavior worth knowing: the decoder is case-sensitive and requires the semicolon. Browsers will resolve © and even a bare & without a trailing semicolon in some parsing contexts, thanks to legacy compatibility rules; this decoder resolves neither. In practice that's fine — anything a modern tool generates is lowercase and terminated — but if you're decoding scraped HTML from an ancient CMS, that's the edge you'll hit.
Numeric References: Decimal and Hexadecimal
Any Unicode character can be written as a numeric character reference — decimal like — or hex like — (both are an em-dash). The decoder resolves both forms. This is the escape hatch for characters that have no name in the standard, and it's the form you'll meet constantly in API responses and RSS feeds, where ’ (right single quote) is practically a signature. Hex references map directly to Unicode code points — U+2014 is — — which is why I prefer them when I'm cross-referencing against a Unicode chart.
Honest limitation, since you'll notice it within a minute of using the tool: the numeric encode mode emits decimal only. There's no hex-output option. Decoding — works fine; asking the encoder to produce it doesn't. The two forms are semantically identical to every browser, so this costs you nothing functionally, but if your codebase standardizes on hex you'll be converting by hand. It's on my list. Out-of-range references get caught rather than mangled — � is above the Unicode maximum and returns an explicit error instead of a replacement character.
Full Unicode Coverage
Emoji, CJK characters, Arabic, combining diacritics, the works. If it has a code point, the tool can express it as an entity and resolve it back. This matters more than you'd think for localization work — I've debugged German umlauts arriving as ü from one translation vendor and as raw UTF-8 ü from another, in the same import file. A tool that chokes outside Latin-1 is useless for that. Characters above U+FFFF (emoji live up there) are handled correctly as single code points, not mangled surrogate pairs.
Untangles Double-Escaped Text
The &amp; problem. When two layers of a pipeline both escape, & becomes &amp; — and three layers gives you &amp;amp;. Decoding once peels off exactly one layer, so you can run the decoder repeatedly and watch the onion unwrap: &amp;amp; → &amp; → & → &. Counting the passes tells you how many layers of your stack are escaping, which is precisely the diagnostic I needed during that four-times-escaped WP Adminify footer bug. One decode per layer. It's the fastest way I know to localize where in a pipeline the extra escaping happens.
Side-by-Side Panes With Character Counts
Input on the left, output on the right, character counts above both. That count pair does more work than it sounds like: escaping is an expanding operation, so if you encode 40 characters and get 44 back, exactly one special character was touched. When I'm auditing whether a template already escaped something, the delta tells me before I've read a single character of output. Encode, Decode, Swap, and Clear are buttons — there's no live-as-you-type conversion, which I'll admit is a deliberate tradeoff I sometimes regret. Explicit actions mean you always know which direction produced the text you're looking at, and with escaping bugs that ambiguity is the whole problem. But for exploratory poking, a keystroke-driven version would genuinely be nicer.
100% Client-Side — Nothing Uploaded
Everything runs in your browser. No request, no server, no logs. This isn't a nice-to-have: the text you're escaping is often exactly the text you shouldn't paste into random websites — user-generated comments with real names, email templates with customer addresses, support ticket content. I've written before about why this matters in our data privacy guide; the short version is that a converter which uploads your input is a data processor you never vetted. The toolz.dev entity tool works offline once loaded. Airplane mode is a valid test — try it.
How to Use the HTML Entity Encoder and Decoder
Step 1: Open the Tool and Paste Your Text
Go to toolz.dev/tools/html-entities and paste your input — a code snippet, a mangled RSS excerpt, an email template fragment, whatever. There's no size ceiling worth worrying about for normal use; I've pasted entire rendered plugin changelogs in. Since processing is client-side, sensitive content is fine here.
Step 2: Pick Encode or Decode
Encoding turns raw characters into entities (< → <) — use it when you want markup to display as text. Decoding resolves entities back to characters (& → &) — use it when you're reading escaped content. If you're not sure which state your text is in, decode first and see what changes. Unchanged output means it was already plain.
Step 3: Choose the Reference Style (When Encoding)
The mode dropdown has exactly three options, and the choice matters more than it looks. Named encodes everything it has a name for and falls back to decimal for the rest — readable in source and diffs, but it also escapes ©, —, é and every other non-ASCII character, which bloats the output if you're on UTF-8 anyway. Numeric does the same coverage in pure decimal. Special chars only touches nothing but & < > " ' and leaves your accents, em-dashes, and emoji as raw UTF-8 — this is the one I use for real content, and it's the mode that matches what htmlspecialchars() does in PHP. Note that ' always comes out as ', never ', in all three modes; that's deliberate, since ' is undefined in HTML 4 and older email clients still choke on it.
Step 4: Check the Output, Then Copy
Hit Encode or Decode and read the right-hand pane. For decoding jobs, look specifically for leftover & sequences — a survivor means the text was double-escaped, so hit Swap and decode again. When it reads clean, copy the result into your template, CMS, or code. For repeat jobs, round-trip once (encode then decode) to confirm nothing lossy happened; with special-chars-only mode the round trip is exact.
Named vs Numeric Entities — and the Five Characters That Actually Matter
Let's get the terminology straight, because "HTML entity" gets used loosely. The WHATWG HTML Standard — the living spec that defines how browsers actually parse HTML — specifies a table of named character references: over 2,200 names like , —, …, →, each mapping to one or two Unicode code points. Separately, numeric character references let you address any code point directly: decimal (—) or hexadecimal (—). Same em-dash, three spellings.
Here's my opinionated take, sharpened by years of WordPress work: out of those 2,200+ names, only five characters actually matter for correctness and safety. Everything else is typography, and on a UTF-8 page — which is every page you should be shipping in 2026 — you can just type the real character. You don't need —; you need —. The five that matter are the ones with syntactic meaning in HTML:
| Character | Entity | Why it matters |
|---|---|---|
& |
& |
Starts every entity — the escape character itself |
< |
< |
Opens tags |
> |
> |
Closes tags |
" |
" |
Delimits double-quoted attributes |
' |
' |
Delimits single-quoted attributes |
Note the last row: ', not '. The name ' is valid in HTML5, but it wasn't part of HTML4, and old tooling (and old email clients — more on those later) can trip on it. The numeric form works everywhere. This is the kind of pedantry that saves you a confusing bug report.
Order of operations is the whole game. When encoding, & must be escaped first. If you escape < to < and then escape ampersands, you'll convert your own output into &lt; — congratulations, you've double-escaped. Decoding is the mirror image: & must be resolved last, or &lt; becomes < becomes < and you've under-decoded (or worse, re-introduced live markup from text that was deliberately escaped). Nearly every hand-rolled escaping bug I've reviewed — including my own — is an ordering bug.
Context matters, and this is where escaping meets security. The OWASP Cross-Site Scripting Prevention Cheat Sheet is blunt about it: HTML entity encoding is the correct defense for the HTML body and attribute contexts, but it is not sufficient for JavaScript strings, URLs, or CSS. < inside a <script> block doesn't decode — script content isn't parsed for entities — so entity-encoding does nothing useful there. Each context needs its own encoder: entity encoding for HTML, \uXXXX escaping for JS strings, percent-encoding for URLs (that's what our URL Encoder/Decoder is for). Using the right encoder in the wrong context is the classic way sanitized-looking code stays exploitable.
On the PHP side, know your two functions. htmlspecialchars() escapes only the five specials (pass ENT_QUOTES or you miss the single quote — a real gotcha). htmlentities() escapes everything that has a named entity, turning ü into ü. On UTF-8 pages, htmlentities() is almost always the wrong choice; it bloats output and mangles content when charsets are misdeclared. WordPress wraps this sensibly:
echo esc_html( $footer_text ); // body context
echo '<a title="' . esc_attr( $title ) . '">'; // attribute context
esc_html() and esc_attr() both escape the five specials with the right flags, chosen per context — exactly the late-escaping discipline OWASP prescribes. The rule I drill into every code review: escape at output time, in the output's context, exactly once.
Which brings it home: with UTF-8, you rarely need entities for typographic characters at all. You need them for markup-significant characters and for untrusted input. Everything else is legacy habit.
Common Use Cases
Displaying Code Snippets in Blog Posts and Docs
Write a tutorial containing <script> or <?php and paste it into a CMS raw, and the browser will try to execute or swallow your example instead of displaying it. Every code sample in an HTML context needs <, >, and & encoded. I do this constantly for plugin documentation — readme HTML, knowledge-base articles, inline examples in admin UI help tabs. The workflow: write the snippet, run it through the entity encoder, paste the escaped version inside <pre><code>. Thirty seconds, and your <script> displays as <script> instead of vanishing into the DOM. If you're building out a docs workflow generally, our coding tools guide covers the rest of the toolbox around this.
Cleaning Up Double-Escaped Text from Databases and Feeds
The &amp; plague. It shows up when a CMS escapes on save, a plugin escapes on render, and a caching layer helpfully escapes once more. I once shipped a WP Adminify changelog where the wordpress.org readme parser and my own build script disagreed about who escapes — the rendered changelog had 23 visible &s in it before a user emailed me. RSS feeds are worse; feed content is often escaped HTML inside XML, so consumers routinely over- or under-decode it. The fix is diagnostic decoding: paste the broken text, decode one pass at a time, count how many passes until it's clean. That count equals the number of escaping layers — now you know exactly how many pieces of your pipeline are touching the text, and you can find the redundant one.
Preparing User-Generated Content Safely
Comments, review text, profile bios, support tickets — anything a user typed is untrusted, and it frequently contains PII: real names, emails, addresses. Two concerns collide here. First, safety: that content must be entity-encoded at output or you're one <img onerror=...> away from stored XSS (ask me about the settings field I nearly shipped). Second, privacy: when you're debugging why a specific user's bio breaks your layout, you're handling their personal data — pasting it into a server-side converter means shipping PII to a third party. The toolz.dev tool processes everything locally, so testing real problem strings is safe. Encode the sample, inspect what your template should have produced, diff against what it did produce.
Email HTML Templates
Email HTML is web development with a 20-year-old rendering engine. Some clients handle raw UTF-8 fine; others — depending on how your ESP sets transfer encodings — garble typographic characters into mojibake. The defensive convention many email developers still follow: encode non-ASCII typography as entities (—, ’, for spacing hacks) so the bytes on the wire are pure ASCII. And remember ' over ' — Outlook's older engines are exactly the tooling that never learned HTML5 names. Since templates get personalized with customer names and addresses, this is again content I'd only run through a client-side tool. Encode the template chrome once, keep merge fields raw, escape them at merge time.
Decoding Scraped Content and API Responses
Scrape a page or consume a sloppy API and you'll drown in ’, “, &, and . Some APIs return entity-encoded strings inside JSON — a format that needs no HTML escaping at all — so you get artifacts like "title": "Fish & Chips". Before that data goes into your own database, decode it to clean UTF-8; store canonical text, escape at output. I hit this constantly when importing content into Laravel apps: decode entities first, then pretty-print and inspect the payload with the JSON Formatter. Doing it in the other order means reading JSON where every apostrophe is seven characters long. If the payload is base64-wrapped on top of that — some webhook providers do this — the Base64 Converter handles the outer layer, and our Base64 encoding guide explains why that wrapping exists.
Localizing Content with Special Characters
Translation files arrive in every state imaginable. One vendor sends clean UTF-8 ü; another sends ü; a third sends ü; occasionally you get all three in one PO file. Before importing, I normalize everything to raw UTF-8 with a decode pass — canonical storage, consistent search, sane diffs. The same applies to RTL punctuation, CJK brackets, and accented Latin in shipped strings. Decode on import, store real characters, and let your output layer escape only the five specials. Your translators will also thank you: über is not a word anyone should have to proofread.
Named vs Decimal vs Hex vs Raw UTF-8: Which Should You Use?
| Form | Example (em-dash) | Readability | Browser support | When to use |
|---|---|---|---|---|
| Named entity | — |
Good — self-describing | Universal for HTML4-era names; HTML5-only names (like ') fail in old tooling |
The five specials; legacy contexts like email HTML |
| Decimal reference | — |
Poor — it's a number | Universal, including ancient parsers | Characters without names; maximum-compatibility escaping (') |
| Hex reference | — |
Poor, but maps to Unicode code points | Universal in anything remotely modern | When cross-referencing Unicode charts or specs |
| Raw UTF-8 | — |
Perfect | Universal on properly declared UTF-8 pages | Everything typographic — this should be your default |
My stance, plainly: write raw UTF-8 for typography, reserve entities for the five specials and untrusted input. A document full of — and … is a document nobody can proofread, and it signals a workflow that hasn't trusted its charset declarations since 2008. Modern stacks — WordPress, Laravel, Next.js, every database you'd choose today — are UTF-8 end to end. Type the real character.
Where entities earn their keep: &, <, >, ", and ' for anything that could be interpreted as markup, always, no exceptions, applied at output time. And in hostile rendering environments — email clients, feeds consumed by unknown parsers — numeric references are the paranoid-but-justified choice because they predate every compatibility argument. Between decimal and hex, it's taste; I lean hex because — matches U+2014 and I can stop doing base conversions in my head.
Frequently Asked Questions
What is an HTML entity?
An HTML entity is a text sequence that represents a character instead of writing the character directly. It starts with an ampersand and ends with a semicolon. There are named references like & and ©, and numeric references like © (decimal) or © (hex) that point at a Unicode code point. Browsers resolve them while parsing, so < displays as a less-than sign instead of opening a tag. They exist so you can show characters that would otherwise be interpreted as markup.
Which characters must be escaped in HTML?
Five: the ampersand, less-than, greater-than, double quote, and single quote — written as &, <, >, ", and '. Ampersand, because it starts entities; the angle brackets, because they delimit tags; the quotes, because they delimit attribute values. In element body text you can get away with just the first three, but escaping all five everywhere is the habit that never bites you. Everything else — accents, dashes, emoji — can be raw UTF-8 on a properly declared page.
What is the difference between < written as a named entity and <?
Nothing, once the browser parses them — both produce a less-than sign. The named form is a lookup in the WHATWG standard's table of named references; < addresses Unicode code point 60 directly, and < is the same code point in hex. Named entities are easier for humans to read; numeric references work for all characters, including thousands that have no name. For the common specials, pick whichever your team finds more readable — browsers do not care.
Why does my page show & instead of an ampersand?
Double escaping. Some layer of your stack escaped an already-escaped string, turning & into &. The browser decodes one level and displays the leftover. It usually means two components both think escaping is their job — a CMS on save plus a template on render is the classic pair. Decode the string one pass at a time in a decoder; the number of passes until it reads clean equals the number of layers escaping it. Then make exactly one layer responsible, at output time.
Does escaping HTML prevent XSS?
In HTML body and attribute contexts, yes — entity-encoding untrusted input there is the core defense, because the payload renders as inert text. But it is not sufficient everywhere. The OWASP XSS Prevention Cheat Sheet is explicit that JavaScript strings, URLs, and CSS each need their own context-specific encoding; entity encoding inside a script block does nothing. Escape at output, in the context you are outputting into, using that context's encoder. Entity encoding is one tool in that kit, not the whole kit.
What is the difference between htmlspecialchars and htmlentities in PHP?
htmlspecialchars() escapes only the markup-significant characters — and you should pass ENT_QUOTES so it covers the single quote. htmlentities() converts every character that has a named entity, so accented letters become things like the uuml reference. On UTF-8 pages, htmlspecialchars() is almost always what you want; htmlentities() bloats output and causes mojibake when charsets are misconfigured. WordPress developers mostly avoid the question by using esc_html() and esc_attr(), which apply the right flags per context.
Should I use the apos named entity for apostrophes?
Prefer '. The apos name is valid in HTML5 but was never part of HTML4, so older parsers — including the rendering engines inside some email clients — do not recognize it and will display it literally. The numeric form ' means the same character and works in everything ever shipped. It is an ugly-but-safe choice, which is usually the right trade-off for escaping. If you know your output only ever hits modern browsers, apos is fine; email templates are exactly where you cannot know that.
Is it safe to paste user data into an online entity converter?
Only if the tool processes text in your browser. User-generated content and email templates routinely contain names, emails, and other PII, and a converter that posts your input to a server has just received that data with no agreement in place. The toolz.dev HTML Entities Encoder/Decoder runs 100% client-side — no upload, no logging, and it works offline once the page has loaded. If you cannot verify how a tool handles input, do not paste production data into it.
Escape Once, In the Right Place
If you take one thing from a decade of my escaping mistakes, take this: exactly one layer of your stack should escape, and it should be the output layer. Store clean UTF-8. Escape the five specials at render time, in the context you're rendering into. Every &amp; in production is a map of two components fighting over that job — and every unescaped user string is a stored XSS waiting for a code review that might not happen. Mine almost didn't.
Keep the HTML Entities Encoder/Decoder in your debugging rotation alongside its siblings — the URL Encoder/Decoder for percent-encoding contexts (the URL encoding guide walks through %2520, the percent-encoding cousin of &amp;), the Base64 Converter for wrapped payloads, and the Case Converter for the identifier-renaming grunt work between them. The coding tools guide walks the whole set.
And since the strings you debug are so often someone's actual name or email: everything above runs client-side, nothing uploaded, verifiable in your network tab. That's not marketing — it's the reason I built these tools the way I did. More on that philosophy in the data privacy guide.

