All posts

HTML Encoding: Characters and Charset Issues That Break Your Pages

4 min read

Three causes of HTML encoding bugs, from unescaped angle brackets to charset mismatches, and a systematic way to fix them.

Someone pastes a copyright symbol into a CMS and it shows as ©. Or an email template breaks because an apostrophe turned into a question mark. Blaming the font or the browser is the usual reaction, but the root cause is almost always an encoding mismatch, and there are really only two kinds.

The first is about HTML entities. The second is about character sets. Knowing which one you're dealing with determines how fast you fix it.

Three Characters, 80% of Bugs

Three characters cause the majority of HTML entity bugs: <, >, and &. They have special meaning in HTML. When you use them as regular text, the browser tries to parse them as markup.

The fix is straightforward: &lt; for <, &gt; for >, and &amp; for &. Most templating engines do this automatically. Here's what the difference looks like:

<?= $title ?>                <!-- risky: raw string -->
<?= e($title) ?>             <!-- Laravel, escapes HTML -->
<?= htmlspecialchars($title) ?> <!-- PHP, raw function -->
Character Problem Named Entity
< Interpreted as HTML tag &lt;
> Interpreted as HTML tag &gt;
& Interpreted as entity start &amp;

The problem is when someone bypasses the engine and concatenates strings directly. A category name like "Men's & Women's Shoes" comes from the database, gets plugged into an HTML string builder, and the & produces unexpected markup. This is surprisingly common in CMS platforms that offer raw HTML fields, exactly where you'd least expect it.

A common mistake is encoding only what you see in the rendered output. You fix the & in the product name on the page, but the & in a URL query parameter embedded in the same page stays raw. Different contexts need different encoding.

Edge Cases Beyond the Big Three

Beyond the big three, there are characters that aren't technically required to be encoded but cause issues in practice:

  • Quotation marks inside attribute values. href="http://example.com?q="test"" breaks because the quote ends the attribute prematurely.
  • Non-breaking spaces that look like regular spaces but aren't. They get pasted from Word documents and suddenly your layout has gaps that won't collapse.
  • Em dashes and en dashes. They're valid Unicode, but some older rendering systems can't handle them. The HTML entities &mdash; and &ndash; are safer.

A common pattern in CMS migrations: the script html-encodes the content, but the old system stored raw HTML in attribute values. The migrated content passes validation, but every link with a query parameter breaks silently. The content looks safe and the migration reports no errors. The bugs surface days later.

The HTML encoder on this site handles all of these cases. It converts raw characters to their entity equivalents and shows you what's invisible in your editor.

When the Charset Mismatches

The © problem is different. It's not an entity encoding issue but a charset mismatch. Your editor works in UTF-8, your database uses Latin-1, and the Content-Type header specifies something else entirely.

The mechanism is simple: a character encoded as UTF-8 (like © as bytes C2 A9) gets interpreted as Latin-1, and each byte becomes a separate character. C2 becomes Â, A9 becomes ©. This is called mojibake, and it's a strong signal your encoding stack is inconsistent.

A typical debugging session: you check the server's Content-Type header and see text/html; charset=utf-8, but the page still shows ©. The next place to look is the meta charset tag. If an old <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> sits in a shared template untouched for years, it overrides the HTTP header in some browsers. A common scenario is a legacy app where a single shared header file, last edited during a previous deployment, silently overrides the server configuration. The WHATWG Encoding spec explains how browsers resolve these conflicts.

Encoding vs Escaping: Similar Names, Different Jobs

You'll hear "encoding" and "escaping" used interchangeably. They shouldn't be. HTML encoding converts characters to their entity representation (© to &copy;). Escaping ensures user input is treated as data, not code. It prevents XSS by neutralizing characters that could break out of context.

A quick way to keep them straight: encoding makes characters display correctly, escaping makes input safe to render. A < in user content needs both: encoding turns it into &lt; so it renders as text, and escaping prevents it from being interpreted as markup. In practice, the distinction matters because some tools handle only one side. It's common to see teams end up with three different utility functions for the same encoding problems, one per framework layer, with none covering all the cases.

The HTML encoder handles encoding. For escaping, use your framework's built-in output escaping: Laravel's {{ }}, Twig's {{ }}, React's JSX (which escapes by default). None of these replace a proper Content Security Policy, but they stop the most common injection vectors.

OCMA Tools

Free developer tools. Most features run client-side, your data stays in your browser. Optional accounts unlock extra features.

Most tools run client-side

© 2026 OCMA Tools — Free developer tools

built for developers, by developers