Character checks7 min readExamples run on PHP 8.5.10

ctype_alnum()

Learn how PHP 8.4 ctype_alnum() checks whether a string contains only letters and digits, including its empty-string result, locale caveat and integer pitfalls.

Signature

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

Purpose

ctype_alnum() answers one narrow question: is every character in a string either a letter or a digit? It is a quick check for identifiers such as product codes, simple usernames or tokens that must not contain spaces, punctuation or symbols. It belongs to the ctype extension, which tests characters by class rather than by pattern, so no regular expression is needed.

Signature

Parameters and defaults

$text is the only parameter and has no default. It is meant to be a string. The function examines each character in turn and returns true only if all of them are alphanumeric.

The manual describes a legacy behavior for integers. An int from -128 to 255 is treated as the ASCII value of one character, with negative values having 256 added. Any other integer is treated as a string of its decimal digits. As of PHP 8.1.0, passing a non-string argument is deprecated, and the manual says the argument will be interpreted as a string in the future. In new code, pass strings only. Cast with (string) if you want the digits, or use chr() if you really want a character code.

Return value and failure cases

The function returns a bool:

  • true when every character is a letter or a digit.
  • false when at least one character is something else, such as a space, punctuation mark or newline.
  • false for an empty string. This is the case people forget most often.

There is no exception for ordinary string input. A failed check is simply false. Which characters count as letters depends on the current locale. In the default locale this means the ASCII letters A to Z and a to z plus the digits 0 to 9. If you need a predictable, locale-independent rule, state it explicitly, for example by also checking the length or by using a pattern that spells out the allowed characters.

Example 1: Checking several strings

This example runs the function over a mix of inputs and prints the results as JSON. Notice that the empty string and the string containing a space both return false.

Example
<?php
$inputs = ['AbCd1zyZ9', 'foo!#$bar', '', 'abc 123', 'user42'];
$result = [];
foreach ($inputs as $value) {
    $result[$value] = ctype_alnum($value);
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"AbCd1zyZ9":true,"foo!#$bar":false,"":false,"abc 123":false,"user42":true}

Example 2: A small username rule

ctype_alnum() only checks the character class, not the length. Combine it with strlen() to build a complete rule. This validator accepts names of 3 to 12 letters and digits. A trailing newline makes the last input fail, because a newline is not alphanumeric.

Example
<?php
function isValidUsername(string $name): bool
{
    $length = strlen($name);
    return $length >= 3 && $length <= 12 && ctype_alnum($name);
}

$names = ['bob', 'ab', 'robert99', 'robert_99', "abc\n"];
$results = [];
foreach ($names as $name) {
    $results[] = isValidUsername($name);
}
echo json_encode($results), "\n";
Output
[true,false,true,false,false]

Example 3: Integers must be converted deliberately

Because of the legacy integer behavior, a bare number can mean either a character code or a digit string. Make your intent explicit instead. Casting to string checks the decimal digits, and chr() checks a single character with that code.

Example
<?php
$results = [
    ctype_alnum((string) 12345),
    ctype_alnum((string) -5),
    ctype_alnum(chr(65)),
    ctype_alnum(chr(33)),
];
echo json_encode($results), "\n";
Output
[true,false,true,false]

The string "-5" fails because the minus sign is not a letter or digit. chr(65) is "A", which passes, while chr(33) is "!", which does not.

Edge cases

  • Empty string: always false. If an empty value is acceptable in your application, test for it separately before calling the function.
  • Whitespace: spaces, tabs and newlines are not alphanumeric, so untrimmed input from a form or file often fails unexpectedly.
  • Locale: letter classification follows the locale. Do not assume accented or non-Latin letters pass or fail without checking your runtime configuration. For true Unicode handling, ctype is the wrong tool and a Unicode-aware approach such as IntlChar::isalnum() (listed in the manual) is a better fit.
  • Numeric strings: a string like "12.5" fails because of the dot, even though is_numeric() accepts it.

Common mistakes

  1. Passing integers directly. ctype_alnum(300) is read as the digits "300", but ctype_alnum(65) is read as the letter A. This is confusing and deprecated since PHP 8.1. Always cast or convert first.
  2. Forgetting the empty string. A blank field returns false, which may be reported as an invalid character problem instead of a missing value.
  3. Treating it as a length check. A 500-character string of letters passes. Add strlen() limits where size matters.
  4. Expecting Unicode letters to pass. The check is byte and locale based, not a general Unicode letter test.
  5. Skipping trimming. Run trim() on user input when surrounding whitespace should be ignored, but only if that matches your rules.
  • ctype_alpha() checks for letters only.
  • ctype_digit() checks for digits only.
  • setlocale() changes the locale that influences character classes.
  • IntlChar::isalnum() checks a single code point for alphanumeric status.

Recap

ctype_alnum(mixed $text): bool returns true only when a non-empty string consists entirely of letters and digits. Pass strings, never bare integers. Remember that the empty string returns false, that whitespace and punctuation fail, and that length limits and Unicode support are your responsibility.

Tested examples

Checking several strings
<?php
$inputs = ['AbCd1zyZ9', 'foo!#$bar', '', 'abc 123', 'user42'];
$result = [];
foreach ($inputs as $value) {
    $result[$value] = ctype_alnum($value);
}
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"AbCd1zyZ9":true,"foo!#$bar":false,"":false,"abc 123":false,"user42":true}
A small username rule
<?php
function isValidUsername(string $name): bool
{
    $length = strlen($name);
    return $length >= 3 && $length <= 12 && ctype_alnum($name);
}

$names = ['bob', 'ab', 'robert99', 'robert_99', "abc\n"];
$results = [];
foreach ($names as $name) {
    $results[] = isValidUsername($name);
}
echo json_encode($results), "\n";
Output
[true,false,true,false,false]
Integers must be converted deliberately
<?php
$results = [
    ctype_alnum((string) 12345),
    ctype_alnum((string) -5),
    ctype_alnum(chr(65)),
    ctype_alnum(chr(33)),
];
echo json_encode($results), "\n";
Output
[true,false,true,false]