Character checks7 min readExamples run on PHP 8.5.10

ctype_xdigit()

Learn how PHP 8.4 ctype_xdigit() tests whether a string contains only hexadecimal characters, with examples, edge cases and common mistakes.

Signature

ctype_xdigit(mixed $text): bool
Available since PHP 4.0.47.48.08.18.28.38.48.5All versions →

Purpose

ctype_xdigit() answers one narrow question: does every character in a string belong to the hexadecimal alphabet? That alphabet is the decimal digits 0 to 9 plus the letters A to F in either upper or lower case. It is a quick pre-check before you convert hash fragments, color codes, identifiers or escaped bytes, because it needs no regular expression and never raises an error for ordinary string input.

Signature

Parameters and defaults

There is a single parameter, $text, and it has no default value. You are expected to pass a string. The function checks every character of that string, so a single stray byte anywhere makes the whole result false.

The parameter is typed mixed for historical reasons. In older code you may see integers passed in. An integer between -128 and 255 used to be treated as the ASCII code of one character, and any other integer as the string of its decimal digits. Since PHP 8.1 passing a non-string argument is deprecated, and the documentation says a future version will treat the value as a string instead. The safe habit is to cast explicitly with (string) $value, or to call chr() if you really meant a character code.

Return value and failure cases

The function returns true when the string is non-empty and every character is in 0-9, A-F or a-f. It returns false otherwise. It does not throw for a bad string; the failure signal is simply false.

The most surprising failure case is the empty string. ctype_xdigit('') always returns false, so an empty value is never considered a valid hexadecimal string. This is usually what you want for validation, but it differs from the vacuous truth you might expect.

Example 1: basic checks and what fails

The first example runs several inputs through the function and prints the results as JSON, which makes the booleans easy to read.

Example
<?php
$inputs = ['AB10BC99', 'AR1012', 'ab12bc99', '', '0x1F', ' 1f'];
$results = [];
foreach ($inputs as $input) {
    $results[$input] = ctype_xdigit($input);
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"AB10BC99":true,"AR1012":false,"ab12bc99":true,"":false,"0x1F":false," 1f":false}

Notice that 0x1F fails because of the x, and 1f fails because of the leading space. The function does not trim whitespace and does not understand prefixes.

Example 2: validating a CSS hex color

Because ctype_xdigit() only checks the character set, you combine it with other rules. Here a helper requires a leading # and exactly three or six hexadecimal digits.

Example
<?php
function isHexColor(string $value): bool
{
    if ($value === '' || $value[0] !== '#') {
        return false;
    }
    $digits = substr($value, 1);
    return in_array(strlen($digits), [3, 6], true) && ctype_xdigit($digits);
}
$tests = ['#fff', '#A1B2C3', '#12345', '#ggg', 'fff'];
foreach ($tests as $t) {
    echo $t, ': ', isHexColor($t) ? 'yes' : 'no', "\n";
}
Output
#fff: yes
#A1B2C3: yes
#12345: no
#ggg: no
fff: no

The length check and the prefix check are your responsibility. The character check is the only part the function performs.

Example 3: guarding hexdec()

hexdec() silently ignores characters that are not valid hexadecimal digits, which can hide bad input. Validating first keeps the conversion honest.

Example
<?php
$values = ['ff', '7FFF', 'xyz', '-1a'];
foreach ($values as $v) {
    if (ctype_xdigit($v)) {
        echo $v, ' => ', hexdec($v), "\n";
    } else {
        echo $v, ' => invalid', "\n";
    }
}
Output
ff => 255
7FFF => 32767
xyz => invalid
-1a => invalid

The value -1a is rejected because the minus sign is not a hexadecimal digit, even though it looks like a signed number.

Edge cases

  • Empty string: always false, as described above.
  • Whitespace and newlines: any space, tab or trailing newline makes the result false. Trim the input first if you want to tolerate it.
  • Prefixes and separators: 0x, #, colons and dashes are not hexadecimal digits. Strip them before the check, for example when handling MAC addresses.
  • Odd length: abc is valid even though it cannot represent whole bytes. If you need byte-aligned data, test that strlen($text) % 2 === 0 yourself.
  • Integers: ctype_xdigit(255) relies on the deprecated ASCII-code interpretation. Write ctype_xdigit((string) 255) when you mean the digits of the number.
  • Non-ASCII text: characters outside the ASCII hex alphabet are not accepted, so a string with accented letters fails.

Common mistakes

  1. Passing numbers directly. Raw integers trigger a deprecation notice in PHP 8.1 and later, and they may be interpreted as a character code rather than a number. Cast to string first.
  2. Expecting it to validate a number. It checks characters only. It says nothing about range, sign, length or whether a value fits in an integer.
  3. Forgetting the empty string. Code such as if (!ctype_xdigit($x)) treats empty input as invalid, which is often desirable, but be aware that you cannot use the function to accept an optional blank value.
  4. Assuming prefix support. Strings such as 0xFF or #FF need the prefix removed before testing.
  5. Confusing it with ctype_digit(). The latter accepts only 0 to 9, so hexadecimal letters make it fail.
  • ctype_digit() checks for decimal digits only.
  • ctype_alnum() checks for letters and digits.
  • hexdec(), dechex() and bin2hex() convert between hexadecimal text and other representations.
  • IntlChar::isxdigit() performs a similar check on a Unicode code point when the intl extension is available.
  • preg_match() can express more complex formats, such as a fixed length plus a prefix, in one pattern.

Recap

ctype_xdigit() takes a string and returns true only when it is non-empty and made entirely of 0-9, a-f or A-F. It never trims, never accepts prefixes, and returns false for the empty string. Cast non-string input explicitly, add your own length and prefix rules, and validate before calling conversion functions such as hexdec().

Tested examples

Basic checks and what fails
<?php
$inputs = ['AB10BC99', 'AR1012', 'ab12bc99', '', '0x1F', ' 1f'];
$results = [];
foreach ($inputs as $input) {
    $results[$input] = ctype_xdigit($input);
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"AB10BC99":true,"AR1012":false,"ab12bc99":true,"":false,"0x1F":false," 1f":false}
Validating a CSS hex color
<?php
function isHexColor(string $value): bool
{
    if ($value === '' || $value[0] !== '#') {
        return false;
    }
    $digits = substr($value, 1);
    return in_array(strlen($digits), [3, 6], true) && ctype_xdigit($digits);
}
$tests = ['#fff', '#A1B2C3', '#12345', '#ggg', 'fff'];
foreach ($tests as $t) {
    echo $t, ': ', isHexColor($t) ? 'yes' : 'no', "\n";
}
Output
#fff: yes
#A1B2C3: yes
#12345: no
#ggg: no
fff: no
Guarding hexdec()
<?php
$values = ['ff', '7FFF', 'xyz', '-1a'];
foreach ($values as $v) {
    if (ctype_xdigit($v)) {
        echo $v, ' => ', hexdec($v), "\n";
    } else {
        echo $v, ' => invalid', "\n";
    }
}
Output
ff => 255
7FFF => 32767
xyz => invalid
-1a => invalid