Formatting Phone Numbers Correctly by Country
9 min read · updated August 11, 2026
The generated number looks plausible, passes the regular expression the same model wrote for it, and does not dial. Almost always the cause is that one format was used where the other was needed, and the two differ by a digit that is not obviously optional.
Two formats, not one
Every phone number has a canonical machine form and a human display form, and they are not interchangeable. The machine form is E.164: a plus sign, a country calling code, and the national significant number, with no spaces, brackets, hyphens or dots. The display form is whatever people in that country actually write on a business card, which is a different string, sometimes with a different digit count.
The failure in AI-generated output is a blend of the two. You get +44 (0)20 7946 0958 — which is a real convention in British print, where the bracketed zero tells a domestic caller to dial it and an international caller to drop it — passed to a telephony API that expects strict E.164 and rejects the brackets. Or you get +442079460958 printed on an invoice for a UK reader, who has to count digits to work out where the area code ends.
What E.164 actually specifies
ITU-T Recommendation E.164, published by the International Telecommunication Union (ITU-T E.164), defines the international public telecommunication numbering plan. The rules that matter for a data model are short:
- The number is at most 15 digits, counting the country code. There is no minimum that applies globally; short national numbers exist.
- The leading
+is notation, not a digit. It means “what follows is an international number” and is replaced by whatever international access prefix the caller’s country uses —00in most of Europe,011in the North American numbering plan. - The country code is one to three digits and is not self-delimiting in any simple way.
+1is one digit,+44two,+353three. You cannot split a number by slicing a fixed prefix. - E.164 contains no separators at all. Any space, hyphen or bracket in a stored number means the field is holding a display string.
The trunk prefix, and where it is kept
This is the single largest source of wrong digits. Many countries use a national trunk prefix — an extra digit dialled before the area code when calling domestically — and it is not part of the international number. The usual rule is that it is dropped:
United Kingdom national: 020 7946 0958 E.164: +442079460958 Germany national: 030 12345678 E.164: +493012345678 Japan national: 03-1234-5678 E.164: +81312345678 Australia national: (02) 9374 4000 E.164: +61293744000 Russia national: 8 495 123-45-67 E.164: +74951234567
Note Russia: the trunk prefix is 8, not 0, and it is replaced by the country code 7. A conversion routine that assumes “strip a leading zero” produces +78495123456, which is one digit too long and unroutable.
And then the exception that breaks the rule entirely:
Italy (Rome) national: 06 6982 1234 E.164: +390669821234 Italy (mobile) national: 320 123 4567 E.164: +393201234567
Italy keeps the leading zero in the international form. A Rome landline is +39 06 ..., with the zero, because in the Italian numbering plan that zero is part of the significant number rather than a trunk prefix. Stripping it — which is what a general-purpose “remove the leading 0” step does, and what a model will often generate — makes the number invalid. Côte d’Ivoire and a handful of other plans behave similarly.
The fixed-length assumption
The validator a model writes alongside the number is usually worse than the number. The recurring shape is /^\d{10}$/ or /^\+?[1-9]\d{7,14}$/, and the first of those encodes the North American plan as if it were universal.
Real national significant numbers are not fixed length, and in several countries they are not even fixed length within one country. German area codes run from two digits (Berlin is 30, Hamburg 40) to five for small towns, and the subscriber number takes up the remainder, so German numbers vary in total length by several digits. Austrian numbers vary similarly. A regular expression that pins a length will reject valid numbers in a way the user cannot work around, because there is no alternative form of their own number to type.
The only length rule you can safely apply globally is the E.164 maximum of 15 digits. Everything narrower requires per-country metadata, which is precisely what Google’s libphonenumber exists to carry: it holds per-region length and prefix rules and is updated as regulators change them. Its four output formats — E164, INTERNATIONAL, NATIONAL and RFC3966 — are the four things people mean when they say “formatted”. The last is the tel: URI defined in RFC 3966, which is what belongs in an HTML link.
National display conventions
The display form is grouping, and the grouping is national. The same eleven digits are written differently in each of these:
US (212) 555-0123 or 212-555-0123 UK 020 7946 0958 (area code and subscriber, space-separated) France 01 42 68 53 00 (five pairs, always) Germany 030 12345678 (area code, space, subscriber unbroken) Japan 03-1234-5678 (hyphens) China 138 0013 8000 (mobile: 3-4-4)
France is the clearest case of a convention that has nothing to do with the structure of the number: French numbers are always written as five groups of two digits, regardless of where the area code ends, and a French reader shown 0142685300 will read it slowly and suspiciously. The UK and US examples above use ranges that their regulators reserve for fiction and documentation, which is worth knowing when you need example numbers that cannot ring a real person.
What to do instead
- Store E.164, always, in one field. Not a country column plus a local number column: E.164 already contains the country code, and two fields means two ways to be inconsistent.
- Format for display at render time, from the stored E.164 plus the viewer’s locale, using a library with per-region data. Never store a display string.
- Do not ask a model to normalise numbers. Ask it to extract the digits it sees and the country it believes they belong to, then run the conversion in code. The trunk-prefix rules are exactly the kind of per-country exception a model will apply confidently and inconsistently across a batch.
- Accept anything on input. Let the user type spaces, brackets, dots and a leading zero; strip them yourself and tell them what you parsed. Rejecting a number for its punctuation is the same class of mistake as rejecting a real surname for being two letters long.