iKit
Tutorial · 10 min read ·

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 (2026)

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

Related on iKit

Related posts