The E.164 phone number format is the standard way to write any phone number as one unambiguous string: a plus sign, the country code, then the national number with no spaces, punctuation or leading trunk zero, for example +442079460958. It has at most 15 digits (the + doesn't count). If you store, compare or text phone numbers, E.164 is the format your database and your SMS API should agree on. This cheat sheet covers the rules, country examples, normalisation code in Python and JavaScript, and the mistakes behind most phone number bugs.
The rules in one minute
E.164 is an ITU-T recommendation (official text). In practice a valid E.164 string:
- starts with
+ - continues with a country code of 1 to 3 digits, which never starts with 0
- ends with the national significant number: area or mobile code plus subscriber number, without the national trunk prefix (usually
0) - contains only digits after the
+ - is 15 digits long at most
A quick structural check:
^\+[1-9]\d{1,14}$
This only checks shape. +19999999999 passes but isn't a real number, which is why you also want a library that knows each country's numbering plan.
E.164 phone number format examples by country
| Country | Code | Written locally | E.164 |
|---|---|---|---|
| United States | +1 | (202) 555-0143 | +12025550143 |
| Canada | +1 | (416) 555-0199 | +14165550199 |
| United Kingdom | +44 | 020 7946 0958 | +442079460958 |
| Netherlands | +31 | 06 12345678 | +31612345678 |
| Finland | +358 | 040 123 4567 | +358401234567 |
| Sweden | +46 | 070-123 45 67 | +46701234567 |
| Belgium | +32 | 0470 12 34 56 | +32470123456 |
The examples are for illustration only. Notice the pattern: where the national format starts with 0, that zero disappears in E.164. The US and Canada share +1, so the country code alone can't tell you which one a number belongs to; the area code can.
Normalising in Python with phonenumbers
phonenumbers is a Python port of libphonenumber (pip install phonenumbers).
from typing import Optional
import phonenumbers
from phonenumbers import NumberParseException, PhoneNumberFormat
def to_e164(raw: str, default_region: Optional[str] = None) -> Optional[str]:
"""Return an E.164 string, or None if the input isn't a valid number."""
try:
number = phonenumbers.parse(raw, default_region)
except NumberParseException:
return None
if not phonenumbers.is_valid_number(number):
return None
return phonenumbers.format_number(number, PhoneNumberFormat.E164)
default_region is a two-letter region code such as "GB" or "NL", used when the input has no +. Parsing a national number with the right region removes the trunk zero for you:
>>> n = phonenumbers.parse("020 7946 0958", "GB")
>>> phonenumbers.format_number(n, PhoneNumberFormat.E164)
'+442079460958'
>>> n = phonenumbers.parse("06 12345678", "NL")
>>> phonenumbers.format_number(n, PhoneNumberFormat.E164)
'+31612345678'
For +1 numbers, phonenumbers.region_code_for_number(number) tells you whether a valid number belongs to the US, Canada or another country in the North American Numbering Plan.
Normalising in JavaScript with libphonenumber-js
import { parsePhoneNumberFromString } from 'libphonenumber-js';
export function toE164(raw, defaultCountry) {
const phone = parsePhoneNumberFromString(raw, defaultCountry);
if (!phone || !phone.isValid()) return null;
return phone.number; // already E.164
}
toE164('0470 12 34 56', 'BE'); // '+32470123456'
phone.country gives you the two-letter country, which is handy for telling US and Canadian numbers apart.
Common pitfalls
-
Keeping the trunk zero.
+4407700900123is wrong;+447700900123is right. Italy is the classic exception: its landline numbers keep their leading 0 after+39. That's one more reason to use a library instead ofreplace(/^0/, ''). -
The UK "(0)". Websites often show
+44 (0)20 7946 0958. Stripping non-digits gives+4402079460958, which is wrong. Parsing libraries handle it; naive regexes don't. -
International dialling prefixes.
0044(dialled from much of Europe) and011 44(dialled from North America) are dialling instructions, not part of the number. Convert them to+44. -
Storing numbers as integers. You lose the
+and invite overflow and formatting bugs. Store a string;VARCHAR(16)holds any E.164 value. - Invisible characters. Pasted numbers can contain non-breaking spaces or direction marks. Normalise before comparing or looking up users, or the same person can register twice.
-
Extensions.
ext. 204isn't part of E.164. Keep it in a separate field. - Stale metadata. Numbering plans change. Update your phone library regularly or new number ranges will start failing validation.
Checking the result on a real network
Unit tests catch parsing bugs, but it's worth confirming that a number normalised from user input actually receives an SMS. Free public test numbers from Temp SMS show incoming texts on a web page. For example, a UK number for receiving test SMS online lets you check that a +44 mobile round-trips through your form, your database and your SMS provider. Keep it to throwaway test accounts, since anyone can read those messages.
FAQ
How many digits can an E.164 number have?
Up to 15, counting the country code but not the plus sign.
Should I store the plus sign?
Yes. +447700900123 is unambiguous and matches what most SMS APIs expect.
Is E.164 the same as "international format"?
Close, but not identical. International display format adds spaces (+44 20 7946 0958); E.164 has none.
Do US and Canadian numbers both start with +1?
Yes, along with other countries in the North American Numbering Plan. Use the area code or a library's region lookup to tell them apart.
More in this series on SMS verification:
Top comments (0)