toolhq.io

All posts
5 min readby Jameel Haider

Why there is no single correct phone number regex

Every developer eventually writes a phone number regex, and every one of them is wrong for some real number. The reason is simple: there is no universal format. Length, grouping, prefixes, and allowed punctuation differ by country, and the rules change over time as number plans expand. A regex that accepts every valid number and rejects every invalid one does not exist.

The format problem

Consider a handful of valid numbers:

CountryExampleNotes
US(415) 555-267110 digits, area code in parens
UK+44 20 7946 0958variable national length
Germany+49 30 123456variable area and subscriber length
India+91 98765 4321010 digit mobile
E.164+14155552671canonical, no punctuation

A pattern tuned for US numbers rejects the UK one. A pattern loose enough to accept both accepts nonsense like +99 00000. Digit counts alone range from roughly 4 to 15, and the valid ranges within each country are governed by numbering authorities, not by anything a regex can encode cleanly.

E.164 as the anchor

The one standard worth knowing is E.164, the ITU format for international numbers. An E.164 number is a leading +, a country code, and the national number, with no spaces or punctuation, up to 15 digits total. A reasonable regex for a string already in E.164 form is:

^\+[1-9]\d{1,14}$

This says: a plus, a first digit that is not zero (country codes never start with zero), then one to fourteen more digits. It does not know which country codes exist or which national lengths are legal, but it enforces the shape of a plausible international number and nothing more. That restraint is the point.

Pragmatic validation versus normalization

Separate two goals that people conflate.

Validation asks whether a string could be a real phone number. For most forms, a light check is enough: strip formatting, confirm the remaining characters are digits with an optional leading +, and confirm the length is in a sane band, say 7 to 15 digits. Rejecting more than that annoys users with legitimate numbers you did not anticipate.

Normalization converts whatever the user typed into one canonical form, usually E.164, so your database stores a single representation. This is where a regex shines: use one to extract digits and detect a leading + or 00 international prefix, then assemble the canonical string.

// keep a leading +, drop everything that is not a digit
cleaned = input.replace(/[^\d+]/g, '').replace(/(?!^)\+/g, '')

That removes spaces, dashes, dots, and parentheses, and strips any stray + that is not at the start.

When to stop and reach for a library

If you need to know that a number is actually assignable, that +1 555 is a valid US area code, or that a German number has the right subscriber length, a regex is the wrong tool. Those rules live in metadata that changes, maintained by projects like Google's libphonenumber. Use that library for real validation and formatting, and keep your regex for the cheap first pass: shape, length, allowed characters.

The honest design is a loose validator that rarely rejects a real number, a normalizer that produces E.164, and a library for anything stricter. Build and test your shape-checking pattern against numbers from several countries in the Regex Tester before you ship it.