DevToys Web Pro iconDevToys Web ProΙστολόγιο
Μεταφρασμένο με LocalePack logoLocalePack
Αξιολογήστε μας:
Δοκιμάστε την επέκταση προγράμματος περιήγησης:
← Back to Blog

Phone Number Parsing Guide: E.164, libphonenumber, and Validation

8 min read

Phone number validation looks simple until you accept users from more than one country. A field that passes ^\+?[0-9\s\-\(\)]+$ will happily accept +99 000 000 0000 (a country code that does not exist) and reject 020 7946 0958 (a perfectly valid UK landline entered locally). Use the Phone Number Parser to follow along with the examples in this guide.

E.164 — The Storage Standard

ITU-T E.164 is the international numbering plan that defines a canonical format for public telephone numbers. The rules are straightforward:

  • Always starts with +
  • Followed by the country calling code (1–3 digits)
  • Followed by the subscriber number (no spaces, dashes, or parentheses)
  • Total maximum length: 15 digits (excluding the +)
+[country code][subscriber number]

+14155552671    US number (country code 1)
+442079460958   UK number (country code 44)
+819012345678   Japan number (country code 81)
+97250000000    Israel number (country code 972)

E.164 is machine-readable and globally unambiguous. It is the format databases, SMS APIs, VoIP systems, and carrier networks expect. Every phone number you store should be in E.164 format.

Why Regex Validation Fails Internationally

The fundamental problem is that phone number rules are not regular. They require a lookup table of country-specific metadata. Three concrete reasons:

Country calling codes are 1–3 digits. The North American Numbering Plan uses country code 1. The UK uses 44. Israel uses 972. A regex that assumes a fixed-length prefix will reject valid numbers or accept invalid ones.

Subscriber number length varies by country. A US number after the country code is always 10 digits. A UK number is 9 or 10 digits. A short-form French mobile can be 9 digits. No single length rule covers all cases.

Trunk prefixes are stripped in international format. When a UK user types their number locally they write 020 7946 0958 — the leading 0 is a trunk prefix used only for domestic dialing. In E.164 that same number becomes +442079460958 — the trunk 0 disappears. A regex cannot know which leading digits to strip without country-specific rules.

Beyond those three, number ranges are allocated in non-contiguous blocks: some prefixes are mobile, others landline, others premium-rate. Only a metadata database tracks this correctly.

libphonenumber — Google's Canonical Parser

Google's libphonenumber is the reference implementation for phone number parsing and validation. It ships a database of every country's numbering plan — valid number ranges, length rules, trunk prefixes, number types — and a parser that uses it.

The library has been ported to every major language. You almost certainly have access to it:

LanguagePackage
JavaScript / TypeScriptlibphonenumber-js
Pythonphonenumbers
Rubyphonelib
PHPgiggsey/libphonenumber-for-php
Gonyaruka/phonenumbers
Java / Androidcom.googlecode.libphonenumber (original)

Parse / Format / Validate Cycle

The JS port libphonenumber-js exposes three core operations. Install it:

npm install libphonenumber-js

Parse a user-supplied string into a structured phone number object, providing a defaultCountry hint for numbers entered without a country code:

import { parsePhoneNumber } from 'libphonenumber-js';

const phone = parsePhoneNumber('020 7946 0958', 'GB');
// phone.country    => 'GB'
// phone.number     => '+442079460958'  (E.164)
// phone.isValid()  => true

Validate with isValid(). This checks that the number falls within an allocated range for its country — not just that the digit count is correct:

import { parsePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';

isValidPhoneNumber('+14155552671')  // true  — valid US number
isValidPhoneNumber('+1415555000')   // false — too short
isValidPhoneNumber('+99123456789')  // false — country code 99 does not exist

Metadata tradeoff. The full metadata bundle is ~145 KB gzipped. For client-side use there are lighter variants:

  • parsePhoneNumber from 'libphonenumber-js' — full metadata, most accurate
  • parsePhoneNumber from 'libphonenumber-js/min' — ~50 KB, skips some extended validation
  • parsePhoneNumber from 'libphonenumber-js/mobile' — mobile numbers only
  • parsePhoneNumber from 'libphonenumber-js/max' — includes carrier/geocoding data

Display Formatting

Once you have a parsed phone number object, libphonenumber-js can format it for four different display contexts:

import { parsePhoneNumber } from 'libphonenumber-js';

const phone = parsePhoneNumber('+442079460958');

phone.formatInternational()  // '+44 20 7946 0958'
phone.formatNational()       // '020 7946 0958'
phone.format('E.164')        // '+442079460958'
phone.getURI()               // 'tel:+442079460958'
FormatExampleUse When
INTERNATIONAL+44 20 7946 0958Displaying to users, UI labels
NATIONAL020 7946 0958Displaying to users in the same country
E.164+442079460958Database storage, API calls, SMS sending
RFC3966tel:+44-20-7946-0958href="tel:..." links, SIP headers

For href="tel:..." links, use getURI() which returns the RFC3966 form. Most mobile browsers and VoIP clients understand this format.

Storage Recommendation

Always store phone numbers in E.164. The reasons are practical: E.164 is globally unique, indexable, and passes straight to SMS and telephony APIs without transformation. National format storage creates ambiguity — 0207 946 0958 is meaningless without knowing the country.

Three rules for a solid storage strategy:

  • Store E.164 in the database. A VARCHAR(16) column is sufficient (15 digits + the + sign).
  • Display locally at render time. Pass the stored E.164 number through formatNational() or formatInternational() depending on the viewer's locale.
  • Preserve the country hint. Store a separate country column (ISO 3166-1 alpha-2, e.g. GB) alongside the phone number. This lets you format nationally without re-parsing.

Handling Extensions

Business phone numbers often have extensions. The standard ways to represent them are:

+14085551212;ext=5678         (RFC 3966 preferred)
+1 (408) 555-1212 ext. 5678  (human-readable)
+14085551212x5678             (informal shorthand)

libphonenumber-js parses extensions automatically when they appear in common formats. Access the extension via the ext property:

import { parsePhoneNumber } from 'libphonenumber-js';

const phone = parsePhoneNumber('+14085551212 ext. 5678', 'US');
phone.number  // '+14085551212'
phone.ext     // '5678'
phone.getURI()  // 'tel:+14085551212;ext=5678'

When building tel: URIs with extensions, the ;ext= parameter is the correct RFC 3966 form. Most softphones and mobile dialers handle it correctly.

Country Detection

To parse a number entered without a country code (national format), you need a defaultCountry hint. There are several ways to obtain it:

  • IP geolocation — fast and automatic, accurate enough for a default. Use a server-side lookup on first request.
  • Accept-Language header — a rough signal. en-GB suggests UK but does not guarantee it.
  • User-provided country — a country selector next to the phone input. Highest accuracy. Required for financial and identity verification flows.
  • Existing profile data — if the user has a billing address, use its country code.
import { parsePhoneNumber } from 'libphonenumber-js';

// User in France entering a national number
const phone = parsePhoneNumber('06 12 34 56 78', 'FR');
phone.number   // '+33612345678'
phone.isValid() // true

Pitfalls

SMS short codes follow completely different rules. Short codes (e.g. 12345 in the US) are 5–6 digits and are never valid E.164 numbers. isValidPhoneNumber will correctly return false for them. If your application sends to short codes, validate them separately with your SMS provider's rules.

VoIP numbers pass validation but may not be reachable by SMS. libphonenumber validates that a number is in an allocated range, but VoIP numbers (like Google Voice, Twilio provisioned numbers) are valid E.164 numbers. SMS deliverability is a carrier-level concern that requires a number lookup API (e.g. Twilio Lookup, Vonage Number Insight) to determine.

Mobile vs landline detection is not reliable in all regions. The getType() method returns MOBILE, FIXED_LINE, or FIXED_LINE_OR_MOBILE (when the type cannot be determined from metadata alone). In many countries — including the US — all numbers in a given area code can be ported to mobile, so type detection is unreliable.

Possible vs valid. isPossiblePhoneNumber() only checks digit length — fast but imprecise. isValidPhoneNumber() checks the full range allocation — slower but correct. Use isValidPhoneNumber for user input validation.


Parse and validate phone numbers directly in your browser with the Phone Number Parser — supports all E.164 countries, extension parsing, and all four display formats, with no data leaving your machine. For related guides see IBAN Validation Guide and Testers Guide.