Signature
mb_chr(int $codepoint, ?string $encoding = null): string|falsePurpose
mb_chr() takes a Unicode code point, which is just an integer such as 65 for A or 128024 for the elephant emoji, and returns a string holding that character in a chosen encoding. It is the counterpart of mb_ord(), which goes the other way.
The plain chr() function only produces a single byte. That is fine for ASCII, but a character such as € takes three bytes in UTF-8. mb_chr() handles the encoding work so you do not have to assemble bytes by hand.
Signature
Parameters and defaults
$codepoint: the Unicode code point as an integer. Hexadecimal literals such as0x20ACare convenient because they match the usualU+20ACnotation.$encoding: the target character encoding, for example'UTF-8'. The default isnull, which means the current internal encoding is used. The parameter is nullable as of PHP 8.0.0.
Passing the encoding explicitly is a good habit. It makes the result independent of whatever mb_internal_encoding() happens to be set to.
Return value and failure cases
The function returns a string containing the requested character. If the code point cannot be represented in the requested encoding, it returns false. Since PHP 8.0.0, an invalid encoding name throws a ValueError. Before 8.0.0 it emitted a warning instead.
So there are two different failure modes:
- a code point that does not fit the encoding gives
false; - an unknown encoding name gives an exception.
Note that the result is a byte string. In UTF-8, strlen() reports bytes, while mb_strlen() reports characters.
Example 1: single code points in UTF-8
This loop converts three code points to characters. JSON encoding is used so the output is easy to read.
$points = [65, 0x20AC, 128024];
foreach ($points as $point) {
echo json_encode(mb_chr($point, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}"A" "€" "🐘"
The three lines are a plain letter, the euro sign and the elephant emoji.
Example 2: bytes versus characters, and unrepresentable values
The euro sign is one character but three bytes in UTF-8. It does not exist in ISO-8859-1, so the same code point fails there.
$char = mb_chr(0x20AC, 'UTF-8');
echo strlen($char), "\n";
echo mb_strlen($char, 'UTF-8'), "\n";
echo bin2hex($char), "\n";
var_dump(mb_chr(0x20AC, 'ISO-8859-1'));3 1 e282ac bool(false)
The hexadecimal dump e282ac shows the actual UTF-8 bytes. The final var_dump() prints bool(false).
Example 3: building a string and round-tripping
You can append the results of several calls to make a string, then use mb_ord() to recover the code points.
$codes = [72, 105, 33, 128075];
$text = '';
foreach ($codes as $code) {
$text .= mb_chr($code, 'UTF-8');
}
echo json_encode($text, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(array_map(fn($c) => mb_ord($c, 'UTF-8'), mb_str_split($text, 1, 'UTF-8'))), "\n";"Hi!👋" [72,105,33,128075]
The numbers come back unchanged, which confirms that the two functions are inverses for valid input.
Edge cases
- Low values such as
65give the same visible character in UTF-8 and in single-byte encodings like ISO-8859-1, because both agree on ASCII. - Single-byte encodings can only represent a small set of characters. Anything outside that set returns
false. - Values that are not valid Unicode code points cannot produce a character. Treat
falseas the signal and do not assume the call succeeded. - Combining sequences are not created for you. One call yields one code point, so a letter with an accent may need two calls if it is written in decomposed form.
Common mistakes
- Using
chr()for non-ASCII text.chr(0x20AC)does not give a euro sign. It reduces the number to a single byte. - Ignoring
false. Concatenatingfalseinto a string silently adds an empty string, which hides the failure. Check the result with=== falsewhen the input or encoding is not under your control. - Relying on the internal encoding. If you omit
$encoding, the output depends on configuration. Pass'UTF-8'when you mean UTF-8. - Confusing code points with bytes. The number you pass is a Unicode value such as
0x1F418, not a UTF-8 byte sequence. - Misspelling the encoding name. An unknown name throws a
ValueErrorin PHP 8, so it is an exception rather than a quietfalse. - Measuring with
strlen(). For a multibyte result, usemb_strlen()if you want the number of characters.
Related functions
mb_ord()returns the code point of a character and is the direct inverse.mb_internal_encoding()gets or sets the default encoding used when$encodingisnull.chr()creates a single-byte string from a number.IntlChar::ord()returns the code point of a character when the intl extension is available.
Recap
mb_chr() converts a Unicode code point to an encoded character string. Always name the encoding, expect false when the character does not fit that encoding, and remember that an invalid encoding name throws a ValueError. Pair it with mb_ord() to move between characters and numbers.
Tested examples
$points = [65, 0x20AC, 128024];
foreach ($points as $point) {
echo json_encode(mb_chr($point, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}
"A" "€" "🐘"
$char = mb_chr(0x20AC, 'UTF-8');
echo strlen($char), "\n";
echo mb_strlen($char, 'UTF-8'), "\n";
echo bin2hex($char), "\n";
var_dump(mb_chr(0x20AC, 'ISO-8859-1'));
3 1 e282ac bool(false)
$codes = [72, 105, 33, 128075];
$text = '';
foreach ($codes as $code) {
$text .= mb_chr($code, 'UTF-8');
}
echo json_encode($text, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(array_map(fn($c) => mb_ord($c, 'UTF-8'), mb_str_split($text, 1, 'UTF-8'))), "\n";
"Hi!👋" [72,105,33,128075]