Multibyte strings7 min readExamples run on PHP 8.5.10

mb_chr()

Learn how mb_chr() turns a Unicode code point into an encoded character string, when it returns false, and how it pairs with mb_ord().

Signature

mb_chr(int $codepoint, ?string $encoding = null): string|false
Available since PHP 7.2.07.48.08.18.28.38.48.5All versions →

Purpose

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 as 0x20AC are convenient because they match the usual U+20AC notation.
  • $encoding: the target character encoding, for example 'UTF-8'. The default is null, 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.

Example
<?php
$points = [65, 0x20AC, 128024];
foreach ($points as $point) {
    echo json_encode(mb_chr($point, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}
Output
"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.

Example
<?php
$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'));
Output
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.

Example
<?php
$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";
Output
"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 65 give 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 false as 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

  1. Using chr() for non-ASCII text. chr(0x20AC) does not give a euro sign. It reduces the number to a single byte.
  2. Ignoring false. Concatenating false into a string silently adds an empty string, which hides the failure. Check the result with === false when the input or encoding is not under your control.
  3. Relying on the internal encoding. If you omit $encoding, the output depends on configuration. Pass 'UTF-8' when you mean UTF-8.
  4. Confusing code points with bytes. The number you pass is a Unicode value such as 0x1F418, not a UTF-8 byte sequence.
  5. Misspelling the encoding name. An unknown name throws a ValueError in PHP 8, so it is an exception rather than a quiet false.
  6. Measuring with strlen(). For a multibyte result, use mb_strlen() if you want the number of characters.
  • 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 $encoding is null.
  • 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

Single code points in UTF-8
<?php
$points = [65, 0x20AC, 128024];
foreach ($points as $point) {
    echo json_encode(mb_chr($point, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
}
Output
"A"
"€"
"🐘"
Bytes versus characters, and unrepresentable values
<?php
$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'));
Output
3
1
e282ac
bool(false)
Building a string and round-tripping with mb_ord()
<?php
$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";
Output
"Hi!👋"
[72,105,33,128075]