Signature
htmlspecialchars_decode(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): stringPurpose
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
$stringis the text to decode.$flagsis a bitmask. The quote flags decide which quote entities are decoded:ENT_COMPATdecodes"and leaves'alone.ENT_QUOTESdecodes both double and single quote entities.ENT_NOQUOTESdecodes neither.
- The document-type flags
ENT_HTML401,ENT_XML1,ENT_XHTMLandENT_HTML5select how the entities are interpreted.ENT_SUBSTITUTEis 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.
$encoded = '<p class="note">Tom & Jerry</p>';
echo htmlspecialchars_decode($encoded), "\n";<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.
$text = '"Hello" 'there'';
$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";{"default":"\"Hello\" 'there'","compat":"\"Hello\" 'there'","noquotes":""Hello" 'there'"}
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 © and € stay as they are because they are outside the function's scope.
$input = '&lt; © €';
$once = htmlspecialchars_decode($input);
$twice = htmlspecialchars_decode($once);
echo json_encode([$once, $twice], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";["< © €","< © €"]
Edge cases
- Double-encoded text.
&lt;becomes<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écomes back unchanged. - Quote flags matter. If your data contains
'and you passENT_COMPAT, the apostrophe stays encoded. PassENT_QUOTESor rely on the default when you want it decoded. - Plain text passes through. A string without entities is returned as it was.
Common mistakes
- Decoding data and then printing it into HTML. Decoding turns harmless
<script>text back into live markup characters. If the result is going into an HTML page, escape it again withhtmlspecialchars()at output time. - Expecting it to decode everything. People often reach for this function to decode
or accented letters. It will not; usehtml_entity_decode()for that. - Assuming the pre-8.1 default. Older tutorials show
ENT_COMPATas the default, which left single quotes encoded. On PHP 8.4 the default includesENT_QUOTES. - Decoding as a security step. Decoding is not sanitising. It only changes representation.
- Mismatched flags. Encoding with one set of flags and decoding with another can leave quotes half converted. Keep the flags consistent on both sides.
Related functions
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 byhtmlspecialchars()andhtmlentities(), 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
$encoded = '<p class="note">Tom & Jerry</p>';
echo htmlspecialchars_decode($encoded), "\n";<p class="note">Tom & Jerry</p>
$text = '"Hello" 'there'';
$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";{"default":"\"Hello\" 'there'","compat":"\"Hello\" 'there'","noquotes":""Hello" 'there'"}
$input = '&lt; © €';
$once = htmlspecialchars_decode($input);
$twice = htmlspecialchars_decode($once);
echo json_encode([$once, $twice], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";["< © €","< © €"]