Say this plainly before anything else
This pattern is scoped to the North American Numbering Plan (NANP)
— the US, Canada and a handful of Caribbean countries that share the +1
country code and the 10-digit area-code-plus-subscriber-number shape. There is no such thing
as a single regex that correctly validates phone numbers worldwide: the
E.164
international format alone allows anywhere from 7 to 15 digits after the country code, and
individual countries layer their own area-code and trunk-prefix conventions on top of that.
A pattern that tried to encode all of it correctly would essentially have to embed a lookup
table of country-specific rules, which is what dedicated libraries such as Google’s
libphonenumber actually do, in code, not in one regular expression.
Token by token
| Token | Meaning |
|---|---|
^ | Start of the string |
\+? | An optional literal +, for the international-prefix style +1 555… |
1? | An optional literal 1, the NANP country code, with or without the preceding + |
[-.\s]? | An optional separator: hyphen, dot, or a single whitespace character |
\(? | An optional literal opening parenthesis around the area code |
\d{3} | The three-digit area code |
\)? | An optional literal closing parenthesis |
[-.\s]? | Another optional separator, between the area code and the exchange |
\d{3} | The three-digit exchange code |
[-.\s]? | Another optional separator, before the last block |
\d{4} | The four-digit subscriber number |
$ | End of the string |
Every separator and every piece of punctuation is independently optional. That is what
lets 5551234567, 555-123-4567 and (555) 123-4567 all
match the same pattern, but it is also a trade-off: because parentheses are optional rather
than paired, a malformed string like (555 123-4567 (a stray opening parenthesis
with no closing one) still matches, since each \(?/\)? is judged
independently rather than as a matching pair.
What it matches
555-123-4567— hyphen-separated, no country code.(555) 123-4567— parenthesised area code, space then hyphen.+1 555.123.4567— international prefix, dot-separated.5551234567— all ten digits, no punctuation at all.
What it deliberately does not match, and why
| Input | Verdict | Why |
|---|---|---|
12345 | Rejected | Too few digits — not a 10-digit NANP number of any kind |
555-123-456 | Rejected | One digit short in the last block — \d{4} requires exactly four |
call me maybe | Rejected | Not digits at all |
+44 20 7946 0958 | Rejected | A real UK number — correctly rejected, because it is not NANP-shaped, but this is the pattern’s central limitation: it has no way to say “valid, just not for this country” versus “not a phone number” |
555.123.4567 ext 204 | Rejected | Trailing extension text after the last digit block fails the $ anchor — extensions are common enough in business numbers that you may want to strip /\s*(ext\.?|x)\s*\d+$/i before validating, rather than trying to fold it into this pattern |
Why one pattern cannot cover international numbers
Phone numbering is a per-country administrative scheme, not a universal format. Even
just within NANP, the pattern above accepts area codes that do not exist (a NANP area code
cannot start with 0 or 1, something this pattern does not check) because encoding the full
valid range would make an already-verbose pattern considerably harder to read for a small
real-world gain — unallocated area codes are rare enough in practice that most
applications accept the small false-positive rate. Outside NANP, the differences compound:
variable total length, different trunk prefixes (many countries use a leading 0
domestically that is dropped when dialled with the country code), and mobile versus landline
numbers that are sometimes distinguishable only by a prefix specific to that country’s
plan. Google’s libphonenumber, used by Android, iOS, and most serious
phone-input UI libraries, encodes this as per-country metadata rather than as a regex,
precisely because a regex cannot reasonably hold it. If your form needs to accept phone
numbers from anywhere, a library with that metadata, paired with a country selector, is a
better foundation than any single pattern.
Flavour: this is JavaScript’s regex
This pattern targets JavaScript’s built-in
RegExp,
per ECMA-262.
It uses only literals, \d, character classes and ?/{n}
quantifiers — all supported identically in PCRE and Python’s re, so
it needs no changes to move between them. See the
cookbook’s flavour-honesty section for the constructs that do
differ between engines.
Worked examples
1. Normalising before you store it, not just validating it
Three different customers might type 555-123-4567,
(555) 123-4567 and 5551234567 for the same number. All three
pass this pattern, but storing them in three different text shapes makes searching and
deduplication painful later. Strip everything that is not a digit
(value.replace(/\D/g, '')) after validating, and store the 10 (or 11, with
country code) plain digits — format only for display.
2. Redacting phone numbers from pasted text
With g on and the Replace tab, this pattern can scrub phone-number-shaped
text out of a block of pasted support tickets or chat logs before sharing it externally.
Because the pattern is permissive about separators, it will catch more formatting variants
than a stricter one would — useful for redaction, where a false positive (redacting
something that merely looks like a phone number) is a far smaller problem than a false
negative (leaving a real one exposed).
3. Rejecting an obviously fake number in a signup form
000-000-0000 and 123-456-7890 both match this pattern —
it checks digit count and punctuation shape, not whether the number is a real, dialable
line. If you need to filter out these obvious placeholders specifically, add an explicit
denylist check for them; a regex checking “the digits are not all the same” or
“not a simple ascending run” adds complexity most forms do not need for a
problem this narrow.
4. Validating on blur, formatting on display
A common pattern in phone-number input fields: leave the raw field editable and
unformatted while the person types, run this validator only when the field loses focus,
and if it passes, replace the displayed value with a consistently formatted version
((555) 123-4567, say) built from the digits alone. That separates three
concerns this single regex was never meant to handle together — accepting varied
input, validating shape, and presenting a consistent format — into three small,
individually simple steps.
Using this pattern in your own code
The pattern relies only on \d, character classes and ?/{n}
quantifiers, all standard across mainstream regex engines, so it needs no adjustment moving
between a browser form and a server-side check.
// JavaScript
const PHONE_RE = /^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
PHONE_RE.test('(555) 123-4567'); // true
PHONE_RE.test('12345'); // false
# Python's re module — identical pattern text
import re
PHONE_RE = re.compile(r'^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$')
bool(PHONE_RE.match('+1 555.123.4567')) # True
# grep -P, scanning a support-ticket export for anything phone-shaped
grep -Po '\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}' tickets.txt
The command-line example above drops the ^/$ anchors and adds
-o deliberately — on the command line the more common job is finding
phone-shaped substrings inside longer lines, not validating that an entire line is only a
phone number, so the anchored form from the pattern box would be the wrong tool for that
particular job even though the core pattern is identical.
Common mistakes when adapting this pattern
- Bolting a country selector onto this exact pattern. A common,
well-intentioned mistake: add a dropdown for country code, keep this NANP-shaped pattern
underneath, and just swap the leading
1?for whatever code the dropdown selects. The rest of the pattern — the fixed 3-3-4 digit grouping — is specific to NANP and wrong for most other countries’ numbering, so the digit-count portion needs to change per country too, not just the prefix. - Making every separator required instead of optional. Changing
[-.\s]?to[-.\s](dropping the?) to “enforce consistent formatting” also rejects5551234567typed with no punctuation at all, which is one of the more common ways people actually enter a phone number on a mobile keyboard. Normalise the format after validating, rather than restricting what counts as valid input. - Forgetting that
\dis not locale-aware. JavaScript’s\dmatches only the ASCII digits 0–9, even with theuflag set — it does not expand to Arabic-Indic or other Unicode decimal digit forms someone might paste from a phone keyboard set to a different script. If your users might type digits from another numbering system, normalise them to ASCII digits before validating, rather than trying to widen\ditself. - Treating a pattern match as proof of a working number. This is worth repeating in the specific context of adapting the pattern: adding more punctuation variants or a stricter area-code range does not change what the pattern fundamentally is — a shape check. No amount of regex refinement gets you deliverability; only an SMS or voice verification code sent to the number does.
Frequently asked questions
Does this validate international phone numbers?
No. It is scoped to the North American Numbering Plan
(+1). See the section above on why one regex cannot reasonably cover every
country’s numbering scheme, and consider a metadata-driven library such as
libphonenumber if you need real international coverage.
Why does (555 123-4567 (unbalanced parenthesis) still match?
Because \(? and \)? are each
independently optional — the pattern does not enforce that they appear in a pair.
Catching unbalanced parentheses specifically would need an alternation between the
“with parens” and “without parens” forms written out separately,
which most real-world uses of this pattern have not found worth the added
complexity.
Can I extend it to accept an extension, like “ext. 204”?
Yes — append an optional group such as
(?:\s*(?:ext\.?|x)\s*\d{1,5})? before the closing $.
It is kept out of the base pattern here so the core 10-digit shape stays easy to read; add
it only if your data actually contains extensions.
Should I validate on every keystroke or only on submit?
Only on blur or submit, generally. A phone number is typed digit-by-digit, and it will fail this pattern at every incomplete stage until the last character — validating on every keystroke just shows the user a permanent error state while they are still typing.
Does this check whether the number is currently in service?
No, and no client-side regex could — that requires querying a carrier or a third-party lookup service over the network, which is out of scope for a pattern that only inspects the text in front of it.
Why does a leading 1 without a + also match?
Because 1? does not require the preceding
+ — both 1 555 123 4567 and +1 555 123 4567
are common ways people type the country code, so the pattern accepts either, or
neither.
Does 15551234567, with the country code and no separator at all, match?
Yes — 1? allows the leading 1 with
no punctuation around it at all, and every [-.\s]? in the pattern is
optional, so all eleven digits run together still satisfies every group in sequence:
1, then three digits, then three, then four. This is one of the more common
ways a phone number arrives already stripped of formatting from another system, so
accepting it without punctuation is deliberate, not an oversight.
Is my phone number sent anywhere when I test it here?
No. Matching runs inside a same-origin Web Worker, and there is no upload endpoint anywhere on this page. Nothing you paste into the test-text box leaves your browser.
Limitations
- NANP (
+1) only — not a general international pattern. - Does not validate that an area code or exchange code is actually allocated.
- Parentheses are independently optional, not enforced as a matching pair.
- No support for extensions unless you extend the pattern yourself (see FAQ above).
- Cannot confirm the number is in service — that needs a network lookup this client-side tool does not make.
Related
- Regex for email validationThe same “shape, not proof” honesty, applied to addresses
- Regex for an IPv4 addressAnother numeric format with an exact, checkable range per segment
- Regex for an ISO 8601 dateDigit-group validation that still can’t know the calendar
- About this toolWhy it exists and how the tester is built
- JSON formatterAnother text tool that runs entirely in your browser
- All toolsSee everything on this site