Validate Phone Numbers with Regex Across Countries (2026)
How to validate phone numbers with regex across country formats: the E.164 pattern that always works, US patterns, and where regex stops working.
Validate Phone Numbers with Regex Across Countries
Every signup form needs to check a phone number, and the temptation is to paste a regex and move on. The problem is that phone numbers vary wildly by country, so a pattern that works for US numbers quietly rejects half the planet. This post shows the one regex that validates phone numbers across country formats, the stricter patterns for single locales, and the exact point where regex stops being enough.
TL;DR
- Store and validate in E.164:
^\+[1-9]\d{1,14}$accepts any country, max 15 digits. - A US number with separators:
^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$. - Strip formatting first, then validate digits — don't try to match every layout at once.
- Regex checks shape only; it can't confirm a number range exists or is reachable.
- For real validation across countries, use libphonenumber, then verify by SMS.
Why phone number regex is harder than it looks
Phone numbers feel simple until you look at two real ones side by side. The structure, length, and punctuation all shift by country, and the regex you copied off a forum encodes assumptions you never agreed to.
Length and structure change per country
A US number is 10 digits. A UK number is 10 or 11 after the trunk zero. German numbers vary in length within the same country, which is exactly why Google's libphonenumber notes that the leading 49 can be read as either a country code or an area code. Argentina changes the area-code length by city — Buenos Aires uses 11 plus eight digits, while Río Gallegos uses 2966 plus six. No single fixed-length pattern survives contact with this.
Punctuation is decorative, not data
Humans type (415) 555-0123, 415.555.0123, 415 555 0123, and +1 415-555-0123 for the same number. Spaces, dots, parentheses, and hyphens carry no information — they're presentation. The reliable move is to strip everything except digits and a leading + before you validate, so your pattern only judges the part that matters.
"Valid format" is not "real number"
A string can match your regex perfectly and still be unassigned, disconnected, or a landline you can't text. Format validation answers "could this be a phone number?" — not "does this phone number exist?" Keeping those two questions separate is the whole game.
The one regex that always works: E.164
If you only remember one pattern, make it the E.164 one. E.164 is the ITU-T international numbering standard, and it defines a canonical, punctuation-free shape that every phone number on Earth can be reduced to.
What E.164 actually specifies
Per the ITU-T E.164 recommendation, a fully qualified number is a leading +, a country code of one to three digits, then the national number — capped at 15 digits total, digits only, no spaces. So +14155550123 is valid; +1 (415) 555-0123 is the same number in human form. Country codes never start with 0, which is what makes the pattern tight.
The pattern
^\+[1-9]\d{1,14}$
Reading it left to right: a literal +, a first digit 1–9 (country codes don't lead with zero), then 1 to 14 more digits, for a maximum of 15. That's the entire international space in nine characters. Use it after you've normalised input to E.164:
const e164 = /^\+[1-9]\d{1,14}$/;
function toE164(raw) {
// keep a leading +, drop everything else non-digit
const cleaned = raw.replace(/[^\d+]/g, "");
return e164.test(cleaned) ? cleaned : null;
}
toE164("+1 (415) 555-0123"); // "+14155550123"
toE164("415-555-0123"); // null — no country code
Storing numbers in E.164 also makes them safe to drop into a JSON payload or pass between services — you can sanity-check that field with the JSON decoder without worrying that someone's parentheses broke the parse.
Watch the plus sign in URLs
If an E.164 number ever travels in a query string, the + is not literal — in form encoding it decodes to a space. Percent-encode it to %2B first. The URL encoder handles this, and it's a common source of "the country code disappeared" bugs.
How to validate a phone number with regex
Most of the time you don't have clean E.164 input — you have whatever a user typed. Here the right approach is locale-aware: pick the format you actually accept, and be explicit about separators.
How to validate a US phone number
A North American number is three groups: area code, prefix, line. Allowing optional separators and an optional +1:
^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$
This accepts (415) 555-0123, 415.555.0123, 415 555 0123, and +1 415-555-0123, while rejecting 41-323-421. It does not verify that 415 is a live area code — that's a metadata question, not a regex one.
How to validate an international phone number loosely
When you want to accept many countries from a single free-text field but still block obvious junk, validate the characters loosely and the digit count strictly:
^\+?[0-9][0-9\-.\s()]{6,18}[0-9]$
This permits the common separators, requires a digit at each end, and bounds the length. Treat it as a first-pass filter, then normalise to E.164 and re-check with the strict pattern above. MDN makes the same point about the pattern attribute on <input type="tel">: a one-size-fits-all phone pattern isn't practical, so you vary it by the user's locale.
Why \d doesn't mean what you think
\d is not portable. In JavaScript, \d always matches only ASCII [0-9]. In Python's re module, \d matches any Unicode decimal digit by default — including Arabic-Indic numerals like ٤١٥ — unless you pass re.ASCII. A pattern that looks identical can therefore accept different input across engines. For phone validation, write [0-9] explicitly so the behaviour is the same everywhere. (This is one of several engine gaps covered in the regex-flavours post linked below.)
Why regex alone can't fully validate a phone number
Regex tops out at syntax. Beyond a point, correctly validating phone numbers requires data about how each country assigns numbers — and that data changes faster than you'll update a regex.
Numbering plans change constantly
Countries split area codes, add digits, and renumber ranges. Google's libphonenumber FAQ is blunt that confirming a number is "not technically feasible without such a verification step given the complicated international world we live in." The library ships per-country metadata sourced from the ITU, telecom operators, and government authorities precisely because no static pattern keeps up.
Possible vs valid vs reachable
libphonenumber draws a useful distinction: isPossibleNumber checks only the length for a region, while isValidNumber checks the number against assigned ranges. Even a "valid" number isn't guaranteed to be connected to a person — the FAQ notes that in some countries extra trailing digits are ignored, so a wrong number can still appear to dial through. Here's how the layers stack:
| Check | What it answers | Tool |
|---|---|---|
| Format regex | Could this be a number? | E.164 pattern |
| Possible (length) | Right length for region? | libphonenumber |
| Valid (range) | In an assigned range? | libphonenumber |
| Reachable | Does it ring a real phone? | SMS / call |
Each row catches errors the row above it can't. Regex owns the top row only.
What to actually do in production
A pragmatic pipeline looks like this:
- Strip everything except digits and a leading
+. - Run the E.164 regex as a cheap first gate.
- Pass survivors to libphonenumber for length and range validation.
- For anything security-sensitive (signup, 2FA), verify by sending an SMS code.
That last step is the only one that proves the number belongs to the person filling out your form.
Patterns by use case
Different jobs want different strictness. Pick the loosest pattern that still rejects the input you can't handle downstream.
Quick reference
| Use case | Pattern |
|---|---|
| Canonical storage | ^\+[1-9]\d{1,14}$ |
| US with separators | ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$ |
| Loose international | ^\+?[0-9][0-9\-.\s()]{6,18}[0-9]$ |
| Digits only (post-strip) | ^[1-9][0-9]{6,14}$ |
Test before you ship
Phone patterns fail on the edge cases you didn't think of: a missing country code, an extension, a vanity number like 1-800-FLOWERS. Paste your pattern and a column of real and fake numbers into the regex tester and watch which ones match before the pattern reaches production. It's the fastest way to catch a quantifier that's too greedy or a separator you forgot to allow.
References
- ITU-T Recommendation E.164 — The international public telecommunication numbering plan — defines the country-code structure and the 15-digit maximum the canonical pattern enforces.
- libphonenumber FAQ —
isPossibleNumbervsisValidNumber, why regex can't keep up with changing numbering plans, and the per-country metadata sources. - MDN:
<input type="tel">— why a single phonepatternisn't practical and how thetelinput leaves format validation to you. - MDN: the
patternattribute — how the regex is compiled with theuflag and applied to form fields.
Related on iKit
- See how email validation hits the same syntax-only wall — the email equivalent of "format ≠ deliverable," and why the giant RFC regex buys you nothing.
- Match a URL with regex and the edge cases most patterns miss — another validation task where greedy quantifiers and optional parts trip people up.
- Why your regex fails across JavaScript, PCRE, and Python — the full story behind
\dmatching different digits per engine. - The regex cheatsheet of 25 patterns you'll use every week — the anchors, quantifiers, and character classes the phone patterns above are built from.
- Pull the parts out with regex capture groups — once a number validates, use groups to split country code, area code, and line.
Related posts
Convert HEIC to JPG, PNG & WebP in the Browser (2026)
Chrome and Firefox can't open HEIC. Here's how to convert HEIC to JPG, PNG, or WebP entirely in your browser — no upload, no app, EXIF under your control.
Serve AVIF, WebP & JPG With One <picture> Tag (2026)
Use one <picture> tag to serve AVIF, WebP, and JPG so every browser gets the smallest image it can decode. Syntax, fallback order, and the mistakes to avoid in 2026.
How to Match a URL with Regex: 3 Edge Cases (2026)
How to match a URL with regex and survive the three edge cases that break most patterns: trailing punctuation, balanced parentheses, and scheme-less links.