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:
^\d{10}$
10 digits with an optional +1 country code, digits only:
^(?:\+?1)?\d{10}$
The practical pattern, accepts the punctuation people actually type:
^(?:\+?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:
^(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?[2-9]\d{2}[-.\s]?\d{4}$
The practical pattern
^(?:\+?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 likecall me at 4155550132 tomorrowwould match on the substring.(?:\+?1[-.\s]?)?is the optional country code. The\+?allows a literal plus sign,1is 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:
^(?:\+?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).
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"); // falseimport 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$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'); // 0For an HTML form, the pattern drops straight into the pattern attribute, which gives instant client-side feedback with no JavaScript:
<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-0132matches, 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-0132is in the555-01XXrange 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
| Input | Practical pattern | Notes |
|---|---|---|
4155550132 | Match | Bare 10 digits |
415-555-0132 | Match | Dash separators |
(415) 555-0132 | Match | Parentheses and a space |
415.555.0132 | Match | Dot separators |
+1 415-555-0132 | Match | With country code |
+14155550132 | Match | Country code, no separators |
555-0132 | No match | Only seven digits, no area code |
415-555-013 | No match | Subscriber group too short |
415-555-01324 | No match | Too many digits |
1-800-FLOWERS | No match | Letters are not digits |
FAQ
See also
- How to Match Numbers with Regex: integers, decimals, and digit-group patterns
- How to Match an Email Address with Regex: the other field on every signup form
- Regex Anchors: why
^and$are non-negotiable for validation patterns - Regex Capturing Groups and Backreferences: pull the area code and subscriber number out separately
- Regex Cheat Sheet: the wider syntax and engine compatibility reference
External reference: paste the pattern into regex101.com to test it interactively against your own input strings.





