Strings7 min readExamples run on PHP 8.5.10

htmlspecialchars_decode()

Learn how htmlspecialchars_decode() turns a small set of HTML entities back into characters, how its flags control quotes, and where it surprises people.

Signature

htmlspecialchars_decode(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string
Available since PHP 5.1.07.48.08.18.28.38.48.5All versions →

Purpose

htmlspecialchars_decode() is the counterpart of htmlspecialchars(). It takes text in which a few reserved characters were written as HTML entities and converts those entities back to the plain characters. You might use it when text was escaped once too early, for example before storing it, and you now need the raw characters for a plain-text context such as a log line, a CSV cell or a text-only email.

The function is deliberately narrow. It handles only the entities that htmlspecialchars() produces: &, ", ', < and >. Named entities such as © or € are not touched. For the full set, html_entity_decode() is the related tool.

Signature

Parameters and defaults

  • $string is the text to decode.
  • $flags is a bitmask. The quote flags decide which quote entities are decoded:
    • ENT_COMPAT decodes " and leaves ' alone.
    • ENT_QUOTES decodes both double and single quote entities.
    • ENT_NOQUOTES decodes neither.
  • The document-type flags ENT_HTML401, ENT_XML1, ENT_XHTML and ENT_HTML5 select how the entities are interpreted. ENT_SUBSTITUTE is listed among the flags for handling invalid code unit sequences.

The default is ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401. According to the manual, this default changed in PHP 8.1.0; before that it was ENT_COMPAT. Code that runs on PHP 8.4 therefore decodes single-quote entities unless you pass different flags.

Return value and failure cases

The function always returns a string: the decoded text. It does not return false and does not signal problems through exceptions for ordinary input. If nothing in the input matches a decodable entity, you get the same text back.

Example 1: basic decoding

The first example decodes a small escaped fragment. The ampersand, angle brackets and double quotes all become plain characters.

Example
<?php
$encoded = '&lt;p class=&quot;note&quot;&gt;Tom &amp; Jerry&lt;/p&gt;';
echo htmlspecialchars_decode($encoded), "\n";
Output
<p class="note">Tom & Jerry</p>

Example 2: controlling quotes with flags

The same input produces three different results depending on the quote flag. The default behaves like ENT_QUOTES.

Example
<?php
$text = '&quot;Hello&quot; &#039;there&#039;';
$results = [
    'default' => htmlspecialchars_decode($text),
    'compat' => htmlspecialchars_decode($text, ENT_COMPAT),
    'noquotes' => htmlspecialchars_decode($text, ENT_NOQUOTES),
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"default":"\"Hello\" 'there'","compat":"\"Hello\" &#039;there&#039;","noquotes":"&quot;Hello&quot; &#039;there&#039;"}

Example 3: only special entities, one layer at a time

Here the input was escaped twice. Each call removes one layer, and the named entities &copy; and &euro; stay as they are because they are outside the function's scope.

Example
<?php
$input = '&amp;lt; &copy; &euro;';
$once = htmlspecialchars_decode($input);
$twice = htmlspecialchars_decode($once);
echo json_encode([$once, $twice], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["&lt; &copy; &euro;","< &copy; &euro;"]

Edge cases

  • Double-encoded text. &amp;lt; becomes &lt; after one call, not <. The function does not loop until the text is stable, and you usually should not call it repeatedly without knowing how many layers exist.
  • Other entities are preserved. Only the five special entities are converted. A string like caf&eacute; comes back unchanged.
  • Quote flags matter. If your data contains &#039; and you pass ENT_COMPAT, the apostrophe stays encoded. Pass ENT_QUOTES or rely on the default when you want it decoded.
  • Plain text passes through. A string without entities is returned as it was.

Common mistakes

  1. Decoding data and then printing it into HTML. Decoding turns harmless &lt;script&gt; text back into live markup characters. If the result is going into an HTML page, escape it again with htmlspecialchars() at output time.
  2. Expecting it to decode everything. People often reach for this function to decode &nbsp; or accented letters. It will not; use html_entity_decode() for that.
  3. Assuming the pre-8.1 default. Older tutorials show ENT_COMPAT as the default, which left single quotes encoded. On PHP 8.4 the default includes ENT_QUOTES.
  4. Decoding as a security step. Decoding is not sanitising. It only changes representation.
  5. Mismatched flags. Encoding with one set of flags and decoding with another can leave quotes half converted. Keep the flags consistent on both sides.
  • htmlspecialchars() converts the reserved characters to entities and is the inverse operation.
  • html_entity_decode() converts a much larger set of named and numeric entities back to characters.
  • get_html_translation_table() returns the translation table used by htmlspecialchars() and htmlentities(), useful for seeing exactly what is converted.

Recap

htmlspecialchars_decode() reverses htmlspecialchars() for five entities, returns a string, and uses ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 as its default flags in PHP 8.4. Choose the quote flag deliberately, remember that it removes only one layer of encoding, and re-escape the result before placing it in HTML.

Tested examples

Basic decoding of escaped markup
<?php
$encoded = '&lt;p class=&quot;note&quot;&gt;Tom &amp; Jerry&lt;/p&gt;';
echo htmlspecialchars_decode($encoded), "\n";
Output
<p class="note">Tom & Jerry</p>
Controlling quote decoding with flags
<?php
$text = '&quot;Hello&quot; &#039;there&#039;';
$results = [
    'default' => htmlspecialchars_decode($text),
    'compat' => htmlspecialchars_decode($text, ENT_COMPAT),
    'noquotes' => htmlspecialchars_decode($text, ENT_NOQUOTES),
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"default":"\"Hello\" 'there'","compat":"\"Hello\" &#039;there&#039;","noquotes":"&quot;Hello&quot; &#039;there&#039;"}
One layer at a time, other entities untouched
<?php
$input = '&amp;lt; &copy; &euro;';
$once = htmlspecialchars_decode($input);
$twice = htmlspecialchars_decode($once);
echo json_encode([$once, $twice], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["&lt; &copy; &euro;","< &copy; &euro;"]