TechEarl

How to Validate a US Phone Number with Regex

Validate a US phone number with regex. The practical pattern, a stricter NANP version, runnable examples in JavaScript, Python, and PHP, what it still lets through, common mistakes, and a test table.

Ishan KarunaratneIshan Karunaratne⏱️ 10 min readUpdated
Validate a US phone number with regex. Practical pattern, stricter NANP pattern, JavaScript / Python / PHP examples, what it lets through, common mistakes, and a test table.

The practical regex for validating a US phone number that handles almost every real-world input: ^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$. It accepts an optional +1 country code, an optional pair of parentheses around the area code, and dashes, dots, or spaces between the digit groups. So (415) 555-0132, +1 415-555-0132, 415.555.0132, and the bare 4155550132 all match.

There is no single perfect phone regex, for the same reason there is no perfect email regex: a pattern can confirm that an input is shaped like a phone number, but it cannot confirm the number is real, assigned, or reachable. The job of the regex is to catch typos and reject obviously wrong input fast. For anything past that, you strip the formatting and check the digits, or you send a verification text.

Quick reference

Exactly 10 digits, no formatting at all:

code
^\d{10}$

10 digits with an optional +1 country code, digits only:

code
^(?:\+?1)?\d{10}$

The practical pattern, accepts the punctuation people actually type:

code
^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$

The stricter pattern, enforces the NANP rule that an area code and exchange code start with a digit from 2 to 9:

code
^(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$

The practical pattern

code
^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$

Breaking it down left to right:

  • ^ and $ anchor the pattern to the full string. Without them, an input like call me at 4155550132 tomorrow would match on the substring.
  • (?:\+?1[-.\s]?)? is the optional country code. The \+? allows a literal plus sign, 1 is the US country code, and [-.\s]? allows one dash, dot, or whitespace character after it. The whole group is wrapped in (?:...)? so it can be left out entirely.
  • \(? and \)? are the optional parentheses around the area code. The backslashes are required because parentheses are regex metacharacters.
  • \d{3} is the three-digit area code.
  • [-.\s]? is an optional separator: a dash, a dot, or a whitespace character.
  • \d{3} is the three-digit exchange code (also called the central office code).
  • [-.\s]? is the second optional separator.
  • \d{4} is the four-digit subscriber number.

The (?:...) is a non-capturing group. It groups the country-code section so the trailing ? applies to the whole thing, without creating a capture you do not need.

Stricter validation with NANP rules

US numbering follows the North American Numbering Plan. Two rules from that plan are easy to encode in regex and reject a large class of fake numbers:

  • The area code cannot start with 0 or 1.
  • The exchange code cannot start with 0 or 1.

Replacing the first \d of each group with [2-9] enforces both:

code
^(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$

That single change rejects (011) 234-5678, 123-456-7890, and similar inputs that the practical pattern accepts. It is the version I reach for on a signup form, because the cost is one character per group and it catches a real category of bad input.

What regex cannot reasonably encode is the full NANP rule set: reserved service codes like 211 or 911, the 555-0100 through 555-0199 block set aside for fiction, and which area codes are actually in service. Those need a lookup table, not a pattern.

Examples in JavaScript, Python, and PHP

The same practical pattern in three languages. Note that none of them use the multiline flag, which is deliberate (see Common mistakes below).

javascript
const phonePattern = /^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;

phonePattern.test("(415) 555-0132");   // true
phonePattern.test("+1 415-555-0132");  // true
phonePattern.test("415.555.0132");     // true
phonePattern.test("555-0132");         // false
python
import re

phone_pattern = re.compile(r"^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$")

bool(phone_pattern.match("(415) 555-0132"))   # True
bool(phone_pattern.match("+1 415-555-0132"))  # True
bool(phone_pattern.match("555-0132"))         # False
php
$pattern = '/^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/';

preg_match($pattern, '(415) 555-0132');  // 1
preg_match($pattern, '+1 415-555-0132'); // 1
preg_match($pattern, '555-0132');        // 0

For an HTML form, the pattern drops straight into the pattern attribute, which gives instant client-side feedback with no JavaScript:

html
<input
  type="tel"
  pattern="^(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$"
  title="Enter a US phone number, like +1 415-555-0132 or (415) 555-0132"
/>

Engine compatibility

The practical pattern uses only universal regex features: anchors, character classes, quantifiers, escaped literals, and a non-capturing group. It works unchanged in JavaScript, Python, PHP (preg_match), Go, Java, .NET, and Ruby.

One portability note: \d is ASCII-only in JavaScript by default, but in Python, PCRE, .NET, and Java it can match Unicode digits from other scripts unless you opt out. If you need strictly ASCII digits, use [0-9] in place of every \d. For phone validation that is usually the safer choice, since you almost never want to accept a number typed in Devanagari or Arabic-Indic digits.

What the practical pattern still lets through

The pattern checks shape, not validity. A few inputs match that you might not expect:

  • Unbalanced parentheses. (415 555-0132 matches, because \(? and \)? are independent optional characters. The pattern does not require that an opening parenthesis is paired with a closing one.
  • Reserved and fictional numbers. (415) 555-0132 is in the 555-01XX range that is reserved for use in fiction. It is correctly shaped, so it matches.
  • Numbers that are shaped right but not in service. Any unassigned area code passes the practical pattern; the stricter NANP version above narrows this but does not eliminate it.

The fix for all three is the same two-step approach. First strip every non-digit character, then validate the digit count and apply your business rules. For real reachability, send an SMS code and treat the number as verified only once the user enters it back. The regex catches typos; the SMS round-trip catches everything else.

Common mistakes

The bugs I see most often in phone validation code, and the fix for each.

Using the multiline flag. Adding /m in JavaScript or re.MULTILINE in Python changes ^ and $ to match at line boundaries, not just the start and end of the string. An input like not a phone\n4155550132 then matches, because the second line satisfies the anchors. Phone validation runs against a single value, so leave the multiline flag off.

Forgetting the anchors. Without ^ and $, the engine finds a substring match and garbage 4155550132 garbage passes. For validation, always anchor both ends.

Validating before normalizing. A pattern that allows separators is convenient for users but awkward to store. The cleaner design is to strip every non-digit first, then validate the result against ^1?\d{10}$. You get one canonical form in the database and a simpler pattern.

Hardcoding parentheses as required. A pattern with \(\d{3}\) instead of \(?\d{3}\)? rejects 415-555-0132 and 4155550132. Most users do not type parentheses; make them optional.

Capping at 7 digits or accepting letters. Patterns built for 555-0132 style local numbers reject the area code. And 1-800-FLOWERS is a vanity number that a digit-only pattern correctly rejects; if you need to accept it, convert the letters to digits before validating, do not widen the regex.

Trusting the regex as proof the number works. A correctly shaped number can be disconnected, fake, or a landline that cannot receive texts. The regex is a typo-catcher, not a verification step.

Test cases: matches and non-matches

InputPractical patternNotes
4155550132MatchBare 10 digits
415-555-0132MatchDash separators
(415) 555-0132MatchParentheses and a space
415.555.0132MatchDot separators
+1 415-555-0132MatchWith country code
+14155550132MatchCountry code, no separators
555-0132No matchOnly seven digits, no area code
415-555-013No matchSubscriber group too short
415-555-01324No matchToo many digits
1-800-FLOWERSNo matchLetters are not digits

FAQ

See also

External reference: paste the pattern into regex101.com to test it interactively against your own input strings.

TagsRegexValidationPhone NumbersRegular ExpressionsJavaScriptPythonPHP
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Match integers, decimals, signed, scientific, thousands-separated, currency, and percent numbers with regex. JavaScript / Python / PHP examples, engine notes, common mistakes, test table.

How to Match Numbers with Regex

Match integers, decimals, signed, scientific, thousands-separated, currency, and percent numbers with regex. JavaScript / Python / PHP examples, engine notes, common mistakes, test table.

Validate password strength with regex. Length checks, character-class requirements, lookahead patterns for mixed-case/digit/special enforcement, examples in JavaScript, Python, and PHP, engine notes, common mistakes.

How to Validate Password Strength with Regex

Validate password strength with regex. Length checks, character-class requirements, lookahead patterns for mixed-case/digit/special enforcement, examples in JavaScript, Python, and PHP, engine notes, and common mistakes.