Signature
html_entity_decode(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, ?string $encoding = null): stringPurpose
html_entity_decode() converts HTML entities in a string back into the characters they stand for. For example, & becomes &, < becomes < and é becomes é when the encoding can represent it. It is the opposite of htmlentities(). Use it when text arrives in an escaped form, such as stored markup fragments, feed data or an API field, and you need the plain characters.
Decoding is not a safety step. The result may contain < and > characters that browsers treat as markup, so escape it again with htmlspecialchars() before printing it into an HTML page.
Signature
Parameters and defaults
$stringis the input text containing entities.$flagsis a bitmask. Quote flags decide which quote entities are decoded:ENT_QUOTESdecodes both double and single quotes,ENT_COMPATdecodes only double quotes, andENT_NOQUOTESdecodes neither. A document-type flag chooses the entity table:ENT_HTML401,ENT_XML1,ENT_XHTMLorENT_HTML5.ENT_SUBSTITUTEis part of the default value as well.$encodingnames the character set, such as'UTF-8'. When it isnullor omitted, PHP uses thedefault_charsetsetting. Passing the encoding explicitly makes the code independent of configuration. An unrecognized charset name emits a warning and the default encoding is used instead.
Since PHP 8.1 the default flags are ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401; before that they were ENT_COMPAT. Since PHP 8.0 the $encoding argument is nullable.
Return value and failure cases
The function always returns a string, the decoded text. It does not return false and does not throw for ordinary input. Entities that it cannot or should not decode are left untouched. That covers unknown names such as &bogus;, entities that are not valid for the chosen document type, and entities whose characters do not exist in the chosen encoding.
Example 1: basic decoding
With the default flags, named and numeric entities for quotes are decoded along with & and the angle-bracket entities.
$encoded = 'Tom & Jerry <3 "cheese" 'pie'';
echo html_entity_decode($encoded), PHP_EOL;Tom & Jerry <3 "cheese" 'pie'
Example 2: controlling quotes with flags
The same input gives three different results depending on the quote flag. The JSON output makes the exact strings visible.
$text = '"Hi" 'there' &';
echo json_encode([
'default' => html_entity_decode($text),
'compat' => html_entity_decode($text, ENT_COMPAT | ENT_HTML401, 'UTF-8'),
'noquotes' => html_entity_decode($text, ENT_NOQUOTES | ENT_HTML401, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;{"default":"\"Hi\" 'there' &","compat":"\"Hi\" 'there' &","noquotes":""Hi" 'there' &"}
Note that & is decoded in all three cases. The quote flags affect only quote entities.
Example 3: non-breaking spaces, double encoding and unknown entities
$nbsp = html_entity_decode(' ', ENT_QUOTES | ENT_HTML401, 'UTF-8');
$once = html_entity_decode('&lt;');
echo json_encode([
'nbspHex' => bin2hex($nbsp),
'trimmedHex' => bin2hex(trim($nbsp)),
'once' => $once,
'twice' => html_entity_decode($once),
'mixed' => html_entity_decode('&bogus; A A'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;{"nbspHex":"c2a0","trimmedHex":"c2a0","once":"<","twice":"<","mixed":"&bogus; A A"}
The non-breaking space is the code point U+00A0, which is two bytes (c2a0) in UTF-8. It is not the ASCII space that trim() removes, so trim() leaves it in place. The double-encoded &lt; needs two passes to become <. The unknown &bogus; stays as written, while the hexadecimal and decimal numeric entities both become A.
Edge cases
- Document type matters. The set of named entities depends on the document-type flag. For instance, HTML 4.01 does not define
', so chooseENT_HTML5,ENT_XHTMLorENT_XML1if your data uses it. - Encoding matters. An entity is decoded only if its character exists in the chosen charset. A legacy single-byte encoding cannot hold every character that a UTF-8 string can.
- Decoding is one level only. The function does not loop, so double-encoded data stays partly encoded after one call.
- Empty string. An empty input returns an empty string.
Common mistakes
- Treating the output as safe HTML. Decoding can create live tags from escaped text. Re-escape for output.
- Using it on input that was never entity-encoded. Text such as
AT&Tin a legitimate document is meant to display asAT&T, but unrelated strings may be altered unexpectedly if they happen to contain entity-like sequences. - Relying on
trim()to clean . The decoded character is not a regular space. - Mismatching the encoding. Decoding with the wrong charset can leave entities undecoded or produce wrong bytes. Pass
'UTF-8'explicitly for UTF-8 data. - Expecting a partial decode. If you only want to reverse the effect of
htmlspecialchars(),htmlspecialchars_decode()is narrower and handles only the special characters.
Related functions
htmlentities()converts all applicable characters to entities, the inverse of this function.htmlspecialchars()escapes only the markup-significant characters.htmlspecialchars_decode()reverses only that smaller set.get_html_translation_table()shows the table used for conversions.urldecode()decodes URL percent-encoding, which is a different mechanism.
Recap
html_entity_decode() turns entities into characters and always returns a string. Choose quote and document-type flags deliberately, pass the encoding explicitly, remember that unknown entities are left alone and double-encoded text needs more than one pass, and never print decoded text into HTML without escaping it again.
Tested examples
$encoded = 'Tom & Jerry <3 "cheese" 'pie'';
echo html_entity_decode($encoded), PHP_EOL;Tom & Jerry <3 "cheese" 'pie'
$text = '"Hi" 'there' &';
echo json_encode([
'default' => html_entity_decode($text),
'compat' => html_entity_decode($text, ENT_COMPAT | ENT_HTML401, 'UTF-8'),
'noquotes' => html_entity_decode($text, ENT_NOQUOTES | ENT_HTML401, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;{"default":"\"Hi\" 'there' &","compat":"\"Hi\" 'there' &","noquotes":""Hi" 'there' &"}
$nbsp = html_entity_decode(' ', ENT_QUOTES | ENT_HTML401, 'UTF-8');
$once = html_entity_decode('&lt;');
echo json_encode([
'nbspHex' => bin2hex($nbsp),
'trimmedHex' => bin2hex(trim($nbsp)),
'once' => $once,
'twice' => html_entity_decode($once),
'mixed' => html_entity_decode('&bogus; A A'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;{"nbspHex":"c2a0","trimmedHex":"c2a0","once":"<","twice":"<","mixed":"&bogus; A A"}