Signature
chr(int $codepoint): stringPurpose
chr() creates a string that contains exactly one byte. You give it a number, and it returns the byte stored at that position in a single-byte encoding table such as ASCII. It is the counterpart of ord(), which goes the other way and reads the first byte of a string as a number.
The key point is that chr() works with bytes, not with text characters in general. It knows nothing about UTF-8, UTF-16 or any other encoding. If you want a character from a Unicode code point, use mb_chr() or IntlChar::chr() instead.
Signature
Parameters and defaults
$codepoint is the only parameter and it has no default value. The valid range is 0 to 255. The argument is read as an unsigned byte value, so 65 gives "A" and 97 gives "a".
If the number lies outside 0..255, PHP does not throw an error. It wraps the value into range with the equivalent of a bitwise AND with 255. Negative numbers are moved up by multiples of 256 until they are non-negative. For example, -159 behaves like 97, and 833 behaves like 65.
Return value and failure cases
The function always returns a string whose length is exactly one byte. The byte may be a printable character, a control character such as chr(10) (line feed), or a null byte from chr(0).
There is no false return for out-of-range input. The failure cases are about types: passing a value that cannot be used as an integer, such as an array or a non-numeric string, raises a TypeError. The manual's changelog also says that PHP 8.5.0 deprecates integers outside 0..255, so it is a good habit to keep values inside the range yourself rather than rely on wrapping.
Example 1: building a string from byte values
This loop converts a list of ASCII codes into text. Each call returns one byte and the concatenation operator joins them.
$codes = [72, 101, 108, 108, 111];
$text = '';
foreach ($codes as $code) {
$text .= chr($code);
}
echo json_encode($text, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;"Hello"
Example 2: masking values outside 0..255
PHP reduces values beyond the byte range modulo 256, but as of PHP 8.5 it also emits a deprecation notice. Masking the value yourself with & 0xFF gives the same byte without the notice and makes the intent visible.
$values = [65, 256 + 66, -159, 833];
$chars = [];
foreach ($values as $value) {
$chars[] = chr($value & 0xFF);
}
echo json_encode($chars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;["A","B","a","A"]
The mask turns the second value, 322, into 66, which is "B", and the last two values into 97 and 65.
Example 3: assembling a UTF-8 sequence byte by byte
A single call to chr() cannot make a multibyte character, but you can combine several bytes that together form valid UTF-8. The four bytes below encode the elephant emoji.
$str = chr(240) . chr(159) . chr(144) . chr(152);
echo json_encode([$str, strlen($str), bin2hex($str)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;["๐",4,"f09f9098"]
Notice that strlen() reports 4, because it counts bytes. The hexadecimal view shows the raw bytes clearly.
Edge cases
chr(0)returns a string containing a null byte. PHP strings can hold null bytes safely, but they may be awkward when passed to other systems.- Codes 128 to 255 do not produce a meaningful standalone character in UTF-8. A lone byte such as
chr(200)is usually invalid UTF-8, and functions likejson_encode()may fail on it unless it is part of a valid sequence. - Codes below 32 are control characters, for example tab (9), line feed (10) and escape (27). They are often invisible when printed.
- For a single formatted byte,
sprintf('%c', 27)is an alternative that fits naturally inside a larger format string.
Common mistakes
- Expecting Unicode code points.
chr(8364)does not return the euro sign. It wraps to a byte (8364 modulo 256 is 172) and gives a meaningless single byte. Usemb_chr(8364, 'UTF-8')for that. - Relying on wraparound. Out-of-range values are deprecated as of PHP 8.5.0. Validate or mask the value explicitly, for example with
$n & 0xFF, so your intent is visible. - Treating the result as a character count. The returned string is one byte long, and a sequence of such strings is measured in bytes, not characters.
- Passing text.
chr('A')is not a way to get the code of"A"; that is the job oford(). A non-numeric string raises aTypeError. - Printing raw control bytes. Output containing
chr(0)orchr(27)may look empty or confuse a terminal. Convert withbin2hex()orjson_encode()when you need to inspect it.
Related functions
ord()returns the byte value of the first byte of a string.sprintf()with%cformats an integer as a byte inside a template.mb_chr()returns a character from a Unicode code point in a chosen encoding.IntlChar::chr()returns a Unicode character from a code point when the intl extension is available.bin2hex()helps you inspect the bytes thatchr()produced.
Recap
chr(int $codepoint): string returns one byte for a number in 0..255. Out-of-range integers wrap modulo 256 but are deprecated as of PHP 8.5, so mask them explicitly. It is byte-oriented and encoding-unaware, so use the multibyte functions when you need Unicode characters.
Tested examples
$codes = [72, 101, 108, 108, 111];
$text = '';
foreach ($codes as $code) {
$text .= chr($code);
}
echo json_encode($text, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;"Hello"
$values = [65, 256 + 66, -159, 833];
$chars = [];
foreach ($values as $value) {
$chars[] = chr($value & 0xFF);
}
echo json_encode($chars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;["A","B","a","A"]
$str = chr(240) . chr(159) . chr(144) . chr(152);
echo json_encode([$str, strlen($str), bin2hex($str)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;["๐",4,"f09f9098"]