Regex Generator · Guides

Phone Number Regex: Why You Should Not Write Your Own

A few years ago I reviewed a signup form that validated phone numbers with this pattern: ^\(\d{3}\) \d{3}-\d{4}$. It accepted exactly one format: (555) 123-4567. It rejected 555-123-4567, +1 555 123 4567, and every phone number on Earth outside North America. The company had just launched in the UK. I wish I were exaggerating.

Phone numbers look regular. They are not. Every country runs its own numbering plan with its own digit counts, its own trunk prefixes, and its own rules about which ranges are actually assigned. A regex can check shape. It cannot know numbering plans. That gap is where hand written phone validation goes to die.

What goes wrong with the homemade regex

The typical evolution goes like this. Version one handles your home country. Version two adds an optional country code. Version three tries to handle international numbers and becomes ^\+?[1-9]\d{7,14}$, which accepts +11111111111 and rejects nothing meaningful. Each version is a little more permissive and a little less useful, converging on a pattern that verifies the string contains digits, which you already knew.

The specific traps:

What to use instead: libphonenumber

Google's libphonenumber is the de facto standard, and it exists precisely because this problem is a database problem, not a pattern problem. It ships with per country metadata: valid length ranges, trunk prefix rules, and which prefix ranges are actually assignable. It distinguishes a number that is well formed from a number that is actually possible in a given country.

In JavaScript, the lightweight port is libphonenumber-js:

import { parsePhoneNumberFromString } from 'libphonenumber-js';
const phone = parsePhoneNumberFromString('+1 213 373 4253');
phone.isValid();              // true
phone.formatInternational();  // +1 213 373 4253
phone.formatNational();       // (213) 373-4253

The default metadata build is about 80KB, with smaller 65KB and fuller 145KB variants. That is the entire world's numbering plans in less space than a hero image. When a country changes its plan, you update the library, not your regex.

Where regex still has a role

I am not saying banish regex from phone handling. It has two legitimate jobs:

What regex should not do is decide whether a number is real. It should not pretend to be a numbering plan database.

The practical pattern I recommend

Normalize for machines. Format for humans. And let the library that tracks 200+ numbering plans do the job your regex never could.

Frequently asked questions

Can a single regex validate international phone numbers?
No. A regex can check that a string looks like a phone number, but it cannot know per country numbering plans, trunk prefix rules, or which ranges are assigned. Use libphonenumber, which ships that knowledge as data.
What is E.164 and why should I store it?
E.164 is the international format: a plus sign, the country calling code, and the national number, with no spaces or punctuation, max 15 digits. Storing it gives you one canonical identity per number, which makes deduplication, lookups, and provider APIs reliable.
How big is libphonenumber-js?
The default metadata build is about 80KB, with a 65KB minimal and 145KB full variant. For the accuracy it buys, that is cheap. Update it with each release so numbering plan changes flow through.

Try it on the generator

Theory is nice, but patterns earn trust against real strings. Build the pattern on the homepage, paste your own test cases into the live tester, and watch both sides: what should match and what should not.

Keep reading

Why Most Email Regex Patterns Are Wrong (And What to Use Instead)