Validating that a phone number is a real, correctly formed NANP number takes more than checking for ten digits, and the right approach is nearly identical in every language.
This guide covers what actually makes a North American phone number valid, a regex that enforces the real NANP rules, and production-ready validation examples in JavaScript, PHP, Python, Android and iOS, all built on the same underlying library.
What Makes a Phone Number a Valid NANP Number
The North American Numbering Plan (NANP) governs phone numbers in the United States, Canada, and about twenty other countries and territories, all sharing country code +1. A NANP number is ten digits in the pattern NXX-NXX-XXXX, and the rules are stricter than most validation code assumes:
- Area code (NXX): the first digit must be 2 through 9. No area code starts with 0 or 1.
- Exchange code (NXX): same rule, the fourth digit of the ten must be 2 through 9.
- N11 codes are reserved: 211, 311, 411, 511, 611, 711, 811 and 911 are service codes, not valid area codes or exchanges for subscriber numbers.
- 555-01XX is fictional: numbers like 555-0123 are reserved for movies and documentation and will never be assigned.
A validator that only checks for ten digits accepts numbers like 123-456-7890 and 000-000-0000 that cannot exist on the network. That is the difference between checking a format and validating a real number.
The NANP Regex
This pattern enforces the digit-position rules above, with an optional +1 country code, and tolerates the common separators:
/^(?:\+?1[-.\s]?)?\(?([2-9][0-8][0-9])\)?[-.\s]?([2-9][0-9]{2})[-.\s]?([0-9]{4})$/
Regex gets you a long way for NANP specifically, but it cannot know that an area code has actually been assigned, and it falls apart the moment international numbers enter the form. For that, every platform below leans on the same battle-tested engine: Google’s libphonenumber, the library Android itself uses, ported to each ecosystem.
JavaScript
Quick regex check:
function validateNANP(phoneNumber) {
const regex = /^(?:\+?1[-.\s]?)?\(?([2-9][0-8][0-9])\)?[-.\s]?([2-9][0-9]{2})[-.\s]?([0-9]{4})$/;
return regex.test(phoneNumber);
}
The real solution is libphonenumber-js, a lean port of Google’s library:
npm install libphonenumber-js
import { parsePhoneNumberFromString } from 'libphonenumber-js';
function validatePhoneNumber(input, defaultCountry = 'US') {
const phone = parsePhoneNumberFromString(input, defaultCountry);
return phone ? phone.isValid() : false;
}
validatePhoneNumber('(781) 609-7699'); // true
validatePhoneNumber('123-456-7890'); // false: area code starts with 1
validatePhoneNumber('781-609-769'); // false: too short
Formatting for display comes free:
const phone = parsePhoneNumberFromString('7816097699', 'US');
phone.formatNational(); // (781) 609-7699
phone.format('E.164'); // +17816097699, the format to store
PHP
The same regex works server-side, and you should always validate on the server regardless of what the form did:
function validate_nanp( string $phone ): bool {
$regex = '/^(?:\+?1[-.\s]?)?\(?([2-9][0-8][0-9])\)?[-.\s]?([2-9][0-9]{2})[-.\s]?([0-9]{4})$/';
return (bool) preg_match( $regex, trim( $phone ) );
}
For full validation, giggsey/libphonenumber-for-php is the maintained PHP port of Google’s library:
composer require giggsey/libphonenumber-for-php
use libphonenumber\PhoneNumberUtil;
use libphonenumber\NumberParseException;
function validate_phone( string $input, string $region = 'US' ): bool {
$util = PhoneNumberUtil::getInstance();
try {
$number = $util->parse( $input, $region );
return $util->isValidNumber( $number );
} catch ( NumberParseException $e ) {
return false;
}
}
In WordPress I run this inside the form handler before the lead ever reaches the database, alongside the other server-side checks a public form needs.
Python
The phonenumbers package is the official-style Python port and the standard answer:
pip install phonenumbers
import phonenumbers
def validate_phone(raw: str, region: str = "US") -> bool:
try:
number = phonenumbers.parse(raw, region)
except phonenumbers.NumberParseException:
return False
return phonenumbers.is_valid_number(number)
validate_phone("(781) 609-7699") # True
validate_phone("123-456-7890") # False
Note the two-step distinction the library makes: is_possible_number() only checks length and structure, while is_valid_number() checks the number against actual numbering plan data, which is the one you want for NANP verification.
Android
Android ships Google’s libphonenumber in the platform itself, so Kotlin validation needs no third-party dependency:
import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.NumberParseException
fun validatePhone(raw: String, region: String = "US"): Boolean {
val util = PhoneNumberUtil.getInstance()
return try {
val number = util.parse(raw, region)
util.isValidNumber(number)
} catch (e: NumberParseException) {
false
}
}
For non-Android JVM projects, add the dependency explicitly with implementation("com.googlecode.libphonenumber:libphonenumber:9.0.16") or the current release.
iOS
Swift’s standard choice is PhoneNumberKit, a native Swift implementation of the same rules and metadata:
import PhoneNumberKit
let phoneNumberUtility = PhoneNumberUtility()
func validatePhone(_ raw: String, region: String = "US") -> Bool {
do {
_ = try phoneNumberUtility.parse(raw, withRegion: region)
return true
} catch {
return false
}
}
The library also provides PhoneNumberTextField, a drop-in UITextField that formats as the user types, which handles the UX half of the problem.
Valid Is Not the Same as Reachable
Everything above verifies that a number is correctly formed and belongs to an assigned numbering range. None of it proves a human answers that number. When the business case needs reachability, disposable-number detection, or line type (mobile vs landline vs VoIP), that requires a carrier lookup API such as Twilio Lookup layered on top of the structural validation covered here. Validate structure first, always, so you only pay for lookups on numbers that could exist.