Skip to content

A simpler (and smaller) rewrite of Google Android's libphonenumber library

License

MIT, Apache-2.0 licenses found

Licenses found

MIT
LICENSE
Apache-2.0
LICENSE.Apache
Notifications You must be signed in to change notification settings

likrot/libphonenumber-js

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libphonenumber-js

npm version npm downloads coverage

A simpler and smaller rewrite of Google Android's libphonenumber library: easy phone number parsing and formatting in javascript.

See Demo

LibPhoneNumber

libphonenumber is a phone number formatting and parsing library released by Google, originally developed for (and currently used in) Google's Android mobile phone operating system.

libphonenumber-js is a "lighter" pure javascript rewrite of the original libphonenumber library (written in C++ and Java because those are the programming languages used in Android OS). While libphonenumber has an official autogenerated javascript port which is being maintained by Google, it is tightly coupled to Google's closure javascript utility framework. It can still be compiled into one gigantic bundle which weighs 530 kilobytes (330 kB code + 200 kB metadata) — quite a size for a simple phone number input field on a casual website.

Difference from Google's libphonenumber

  • Much smaller footprint: 130 kilobytes (55 kB code + 75 kB sufficient metadata) vs the original Google's 530 kilobytes (330 kB code + 200 kB full metadata).
  • Can search for phone numbers in text (Google's autogenerated javascript port can't).
  • Doesn't parse alphabetic phone numbers like 1-800-GOT-MILK.
  • Doesn't parse or format special local-only phone numbers: emergency phone numbers like 911, "short codes" (short SMS-only numbers), numbers starting with a * (like *555), etc.
  • Doesn't use hyphens and brackets when formatting international phone numbers (looks cleaner).

Install

via npm

$ npm install libphonenumber-js --save

via yarn

$ yarn add libphonenumber-js

If you're not using a bundler then use a standalone version from a CDN.

"min" vs "max" vs "mobile" vs "core"

This library comes pre-packaged with three flavors of metadata:

  • max — The complete metadata set, is about 140 kilobytes in size (libphonenumber-js/metadata.full.json).

  • min — (default) The smallest metadata set, is about 75 kilobytes in size (libphonenumber-js/metadata.min.json). Doesn't contain regular expressions for advanced phone number validation (.isValid()) and determining phone number type (.getType()) for most countries. Some simple phone number validation via .isValid() still works (basic length check, etc), it's just that it's loose compared to the "advanced" validation (not so strict).

  • mobile — The complete metadata set for dealing with mobile numbers only, is about 105 kilobytes in size (libphonenumber-js/metadata.mobile.json).

To use a particular metadata set import functions from the relevant sub-package: libphonenumber-js/max, libphonenumber-js/min or libphonenumber-js/mobile.

Importing functions directly from libhponenumber-js results in using the min metadata which means loose (non-strict) phone number validation via .isValid() and no getting phone number type via .getType() for most countries.

Sometimes (rarely) not all countries are needed and in those cases the developers may want to generate their own "custom" metadata set. For those cases there's libphonenumber-js/core sub-package which doesn't come pre-wired with any default metadata and instead accepts the metadata as the last argument of each exported function.

Use

Parse phone number

import { parsePhoneNumberFromString } from 'libphonenumber-js'

const phoneNumber = parsePhoneNumberFromString('Phone: 8 (800) 555 35 35.', 'RU')
if (phoneNumber) {
  phoneNumber.country === 'RU'
  phoneNumber.number === '+78005553535'
  phoneNumber.isValid() === true
  phoneNumber.getType() === 'TOLL_FREE'
}

Format phone number

import { parsePhoneNumberFromString } from 'libphonenumber-js'

const phoneNumber = parsePhoneNumberFromString('+12133734253')

phoneNumber.formatInternational() === '+1 213 373 4253'
phoneNumber.formatNational() === '(213) 373-4253'
phoneNumber.getURI() === 'tel:+12133734253'

"As You Type" formatter

import { AsYouType } from 'libphonenumber-js'

new AsYouType().input('+12133734')
// Outputs: '+1 213 373 4'

new AsYouType('US').input('2133734')
// Outputs: '(213) 373-4'

Full-text search

import { findNumbers } from 'libphonenumber-js'

findNumbers(`
  For tech support call +7 (800) 555-35-35 internationally
  or reach a local US branch at (213) 373-4253 ext. 1234.
`, 'US', {
  v2: true
})

// Outputs:
//
// [{
//   number: PhoneNumber {
//     country: 'RU',
//     countryCallingCode: '7',
//     number: '+78005553535',
//     nationalNumber: '8005553535'
//   },
//   startsAt : 22,
//   endsAt   : 40
// }, {
//   number: PhoneNumber {
//     country: 'US',
//     countryCallingCode: '1',
//     number: '+12133734253',
//     nationalNumber: '2133734253',
//     ext: '1234'
//   },
//   startsAt : 86,
//   endsAt   : 110
// }]

Definitions

Country code

"Country code" means either a two-letter ISO country code (like US) or a special 001 country code used for non-geographical entities (as per Google's libphonenumber library). For example, +7 800 555 35 35 phone number belongs to Russia so it has RU country code where as +800 1 1111 1111 phone number could belong to any country so it has 001 country code.

National (significant) number

"National (significant) number" are the national phone number digits (without "national prefix"). For example, +1 213 373 4253 (or (213) 373-4253 in national format) is a US phone number and its national (significant) number is 213 373 4253. Another example is +33 1 45 45 32 45 (or 01 45 45 32 45 in national format) which is a French phone number where they add 0 "national prefix" when writing phone numbers in national format; in this case the national (significant) number is 1 45 45 32 45.

Country calling code

"Country calling code" are the digits between the + and the national (significant) number when the number is written in international format. E.g. for US country calling code is 1 and for France it's 33. Several countries can share the same "country calling code", e.g. NANPA countries like USA and Canada sharing the same 1 country calling code.

API

parsePhoneNumberFromString(string, [defaultCountry])

import { parsePhoneNumberFromString } from 'libphonenumber-js'

const phoneNumber = parsePhoneNumberFromString('Call: (213) 373-42-53 ext. 1234.', 'US')
if (phoneNumber) {
  // ...
}

Returns an instance of PhoneNumber class, or undefined if no phone number could be parsed: for example, when the string contains no phone number, or the phone number starts with a non-existent country calling code, etc.

If a developer wants to know the exact reason why the phone number couldn't be parsed then they can use parsePhoneNumber() function which throws the exact error:

import { parsePhoneNumber, ParseError } from 'libphonenumber-js'

try {
  const phoneNumber = parsePhoneNumber('Call: (213) 373-42-53 ext. 1234.', 'US')
} catch (error) {
  if (error instanceof ParseError) {
    // Not a phone number, non-existent country, etc.
    console.log(error.message)
  } else {
    throw error
  }
}
Possible errors
  • INVALID_COUNTRY — When defaultCountry doesn't exist (parsePhoneNumber('(111) 222-3333', 'XX')) or isn't supported by this library, or when parsing non-international number without a defaultCountry (parsePhoneNumber('(111) 222-3333')), or when international number country calling code doesn't exist (parsePhoneNumber('+9991112223333')).

  • NOT_A_NUMBER — When no phone number was found. For example, when there are no digits ("abcde") or when there's not enough digits (parsePhoneNumber('2', 'US'), parsePhoneNumber('+1')).

  • TOO_LONG — When national (significant) number is too long (17 digits max) or when the string being parsed is too long (250 characters max).

  • TOO_SHORT — When national (significant) number is too short (for ex. 1 digit).

PhoneNumber

PhoneNumber class instance has the following properties:

  • number — The phone number in E.164 format. Example: "+12133734253".
  • countryCallingCode — The country calling code. Example: "1".
  • nationalNumber — The national (significant) number. Example: "2133734253".
  • country — The country code. Example: "US". Will be undefined when no country could be derived from the phone number. For example, when several countries have the same countryCallingCode and the nationalNumber doesn't look like it belongs to any of them.
  • ext — The phone number extension, if any. Example: "1234".
  • carrierCode — The "carrier code", if any. Example: "15". "Carrier codes" are only used in Colombia and Brazil and only when dialing within those countries from a mobile phone to a fixed line number.

PhoneNumber class instance provides the following methods:

format(format: String, [options])

Formats the phone number into a string according to a format.

Available formats:

  • NATIONAL — Example: "(213) 373-4253"
  • INTERNATIONAL — Example: "+1 213 373 4253"
  • E.164 — Example: "+12133734253"
  • RFC3966 (the phone number URI) — Example: "tel:+12133734253;ext=123"
  • IDD"Out-of-country" dialing format. Example: "01178005553535" for +7 800 555 35 35 being called out of options.fromCountry === "US". If no options.fromCountry was passed or if there's no default IDD prefix for options.fromCountry then returns undefined. Pass options.humanReadable: true for a human-readable output (same output as Google liphonenumber's formatOutOfCountryCallingNumber()).

Available options:

  • formatExtension(number, extension) — Formats number and extension into a string. By default returns ${number} ext. ${extension} for almost all countries with rare exceptions of some special cases like ${number} x${extension} for UK.

  • humanReadable: Boolean — Is only used for IDD formatting.

Examples:

import { parsePhoneNumberFromString } from 'libphonenumber-js'

const phoneNumber = parsePhoneNumberFromString('+12133734253')

phoneNumber.format("NATIONAL") === '(213) 373-4253'
phoneNumber.format("INTERNATIONAL") === '+1 213 373 4253'
phoneNumber.format("RFC3966") === 'tel:+12133734253'

// Aliases
phoneNumber.formatNational() === phoneNumber.format("NATIONAL")
phoneNumber.formatInternational() === phoneNumber.format("INTERNATIONAL")
phoneNumber.getURI() === phoneNumber.format("RFC3966")

isPossible()

Checks if the phone number is "possible". Only checks the phone number length, doesn't check the number digits against any regular expressions.

isValid()

Checks if the phone number is "valid". First checks the phone number length and then checks the phone number digits against all available regular expressions.

By default the library uses "minimal" metadata which is only 75 kilobytes in size but also doesn't include the precise validation regular expressions resulting in less strict validation rules (some very basic validation like length check is still included for each country). If you don't mind the extra 65 kilobytes of metadata then use "full" metadata instead (140 kilobytes). Google's library always uses "full" metadata so it will yield different isValidNumber() results compared to the "minimal" metadata used by default in this library.

See an example illustrating different results when using /min vs /max vs /mobile

import { parsePhoneNumberFromString as parseMin } from 'libphonenumber-js'
import { parsePhoneNumberFromString as parseMax } from 'libphonenumber-js/max'
import { parsePhoneNumberFromString as parseMobile } from 'libphonenumber-js/mobile'

// Mobile numbers in Singapore starting from `8`
// can only have the second digit in the range of `0..8`.
// Here the second digit is `9` which makes it an invalid mobile number.
// This is a "strict" (advanced) validation rule and is
// not included in the (default) "min" bundle.

// The basic number length check passes (`8..11`) and the
// "loose" national number validation regexp check passes too:
// `(?:1\d{3}|[369]|7000|8(?:\d{2})?)\d{7}`.
parseMin('+6589555555').isValid() === true

// The "advanced" validation regexp for mobile numbers is
// `(?:8[1-8]|9[0-8])\\d{6}` and possible lengths are `8`.
parseMax('+6589555555').isValid() === false
parseMobile('+6589555555').isValid() === false

See also "Using phone number validation feature" considerations.

getType()

Returns phone number type (fixed line, mobile, toll free, etc) or undefined (if the number is invalid or if there are no phone number type regular expressions for this country in metadata).

By default the library uses "minimal" metadata which is only 75 kilobytes in size but also doesn't include the regular expressions for determining a specific phone number type (fixed line, mobile, toll free, etc) resulting in getType() returning undefined for most countries. If you don't mind the extra 65 kilobytes of metadata then use "full" metadata instead (140 kilobytes). Google's library always uses "full" metadata so it will yield different getNumberType() results compared to the "minimal" metadata used by default in this library.

The list of possible return values

  • MOBILE
  • FIXED_LINE
  • FIXED_LINE_OR_MOBILE
  • PREMIUM_RATE
  • TOLL_FREE
  • SHARED_COST
  • VOIP
  • PERSONAL_NUMBER
  • PAGER
  • UAN
  • VOICEMAIL

See an example illustrating different results when using /min vs /max vs /mobile

import { parsePhoneNumberFromString as parseMin } from 'libphonenumber-js'
import { parsePhoneNumberFromString as parseMax } from 'libphonenumber-js/max'
import { parsePhoneNumberFromString as parseMobile } from 'libphonenumber-js/mobile'

// Singapore valid mobile number.

// The (default) "min" bundle doesn't contain any regexps for
// getting phone number type based on national number (for Singapore).
parseMin('+6584655555').getType() === undefined

// The "max" bundle contains regexps for
// getting phone number type based on national number
// for all possible phone number types.
parseMax('+6584655555').getType() === 'MOBILE'

// The "mobile" bundle contains regexps for
// getting phone number type based on national number
// for mobile phone numbers only.
parseMobile('+6584655555').getType() === 'MOBILE'

class AsYouType(defaultCountry)

Creates a formatter for a partially entered phone number. The defaultCountry is optional and, if specified, is gonna be the default country for formatting non-international phone numbers. The formatter instance provides two methods:

  • input(text) — Takes any text, parses it and appends the digits to the input. Returns the formatted phone number.
  • reset() — Resets the input.
new AsYouType().input('+12133734') === '+1 213 373 4'
new AsYouType('US').input('2133734') === '(213) 373-4'

The formatter instance also provides the following getters:

  • getNumber() — Returns the PhoneNumber. Will return undefined if no national (significant) number digits have been entered so far, or if no defaultCountry has been set and the user enters a phone number not in international format.

  • getTemplate() — Returns the template used to format the output. Digits (and the + sign, if present) are denoted by x-es. Will return undefined if no suitable format was found for the number being entered (or if no national (significant) number has been entered so far).

// National phone number input example.

const asYouType = new AsYouType('US')

asYouType.input('2') === '2'
asYouType.getNumber().number === '+12'
asYouType.getTemplate() === 'x'

asYouType.input('1') === '21'
asYouType.getNumber().number === '+121'
asYouType.getTemplate() === 'xx'

asYouType.input('3') === '(213)'
asYouType.getNumber().number === '+1213'
asYouType.getTemplate() === '(xxx)'

asYouType.input('3734253') === '(213) 373-4253'
asYouType.getNumber().number === '+12133734253'
asYouType.getTemplate() === '(xxx) xxx-xxxx'

// International phone number input example.

const asYouType = new AsYouType()
asYouType.input('+1-213-373-4253') === '+1 213 373 4253'
asYouType.getNumber().country === 'US'
asYouType.getNumber().number === '+12133734253'
asYouType.getTemplate() === 'xx xxx xxx xxxx'
Legacy API (before version 1.6.0)

For legacy API (before version 1.6.0) the formatter instance provides the following getters:

  • country — Phone number country. Will return undefined if the country couldn't be derived from the number.

  • getNationalNumber() — Returns the national (significant) number part of the phone number.

  • getTemplate() — Returns the template used to format the output. Digits (and the + sign, if present) are denoted by x-es. Will return undefined if no suitable format was found for the number being entered (or if no national (significant) number has been entered so far).

// National phone number input example.

const asYouType = new AsYouType('US')

asYouType.input('2') === '2'
asYouType.getNationalNumber() === '2'
asYouType.getTemplate() === 'x'

asYouType.input('1') === '21'
asYouType.getNationalNumber() === '21'
asYouType.getTemplate() === 'xx'

asYouType.input('3') === '(213)'
asYouType.getNationalNumber() === '213'
asYouType.getTemplate() === '(xxx)'

asYouType.input('3734253') === '(213) 373-4253'
asYouType.getNationalNumber() === '2133734253'
asYouType.getTemplate() === '(xxx) xxx-xxxx'

// International phone number input example.

const asYouType = new AsYouType()
asYouType.input('+1-213-373-4253') === '+1 213 373 4253'
asYouType.country === 'US'
asYouType.getNationalNumber() === '2133734253'
asYouType.getTemplate() === 'xx xxx xxx xxxx'

"As You Type" formatter was created by Google as part of their Android OS and therefore only works for numerical keyboard input, i.e. it can only accept digits (and a + sign in the start of an international number). When used on desktops where a user can input all kinds of punctuation (spaces, dashes, parens, etc) it simply ignores everything except digits (and a + sign in the start of an international number).

Google's "As You Type" formatter does not support entering phone number extensions. If your project requires phone number extensions input then use a separate input field for that.

findNumbers(text, [defaultCountry], [options])

Searches for phone numbers in text.

New API (starting from version 1.6.0) returns phone numbers as instances of PhoneNumber class when passed v2: true option. Legacy API (before version 1.6.0) only returns phone numbers as country, phone, ext.

import { findNumbers } from 'libphonenumber-js'

findNumbers(`
  For tech support call +7 (800) 555-35-35 internationally
  or reach a local US branch at (213) 373-4253 ext. 1234.
`, 'US', {
  v2: true
})

// Outputs:
//
// [{
//   number: PhoneNumber {
//     country: 'RU',
//     countryCallingCode: '7',
//     number: '+78005553535',
//     nationalNumber: '8005553535'
//   },
//   startsAt : 22,
//   endsAt   : 40
// }, {
//   number: PhoneNumber {
//     country: 'US',
//     countryCallingCode: '1',
//     number: '+12133734253',
//     nationalNumber: '2133734253',
//     ext: '1234'
//   },
//   startsAt : 86,
//   endsAt   : 110
// }]
Legacy API (before version 1.6.0) example

import { findNumbers } from 'libphonenumber-js'

findNumbers(`
  For tech support call +7 (800) 555-35-35 internationally
  or reach a local US branch at (213) 373-4253 ext. 1234.
`, 'US')

// Outputs:
//
// [{
//   phone    : '8005553535',
//   country  : 'RU',
//   startsAt : 22,
//   endsAt   : 40
// },
// {
//   phone    : '2133734253',
//   country  : 'US',
//   ext      : '1234',
//   startsAt : 86,
//   endsAt   : 110
// }]

By default it processes the whole text and then outputs the phone numbers found. If the text is very big (say, a hundred thousand characters) then it might freeze the user interface for a couple of seconds. To avoid such lags one can employ "iterator" approach using searchNumbers() to perform the search asynchronously (e.g. using requestIdleCallback or requestAnimationFrame).

Asynchronous search example using searchNumbers()

ES6 iterator:

import { searchNumbers } from 'libphonenumber-js'

const text = `
  For tech support call +7 (800) 555-35-35 internationally
  or reach a local US branch at (213) 373-4253 ext. 1234.
`

async function() {
  for (const number of searchNumbers(text, 'US', { v2: true })) {
    console.log(number)
    await new Promise(resolve => setTimeout(resolve, 0))
  }
  console.log('Finished')
}

Java-style iterator (for those still not using ES6):

import { PhoneNumberMatcher } from 'libphonenumber-js'

const matcher = new PhoneNumberMatcher(`
  For tech support call +7 (800) 555-35-35 internationally
  or reach a local US branch at (213) 373-4253 ext. 1234.
`, {
  defaultCountry: 'US',
  v2: true
})

// Search cycle iteration.
const iteration = () => {
  if (matcher.hasNext()) {
    console.log(matcher.next())
    setTimeout(iteration, 0)
  } else {
    console.log('Finished')
  }
}

// Run the search.
iteration()

Although Google's javascript port doesn't have the findNumbers() functionality the Java and C++ ports do. I guess Google just doesn't need to crawl phone numbers on Node.js because they can afford to hire a Java/C++ developer to do that. Still, javascript nowadays is the most popular programming language given its simplicity and user-friendliness. The findNumbers() function provided is a port of Google's PhoneNumberMatcher.java into javascript.

getExampleNumber(country, examples)

Returns an example phone number for a country. Returns an instance of PhoneNumber class. Will return undefined if country doesn't exist or isn't supported by this library.

import examples from 'libphonenumber-js/examples.mobile.json'
import { getExampleNumber } from 'libphonenumber-js'

const phoneNumber = getExampleNumber('RU', examples)

phoneNumber.formatNational() === '8 (912) 345-67-89'

isSupportedCountry(country)

Checks if a country is supported by this library.

isSupportedCountry('RU') === true
isSupportedCountry('XX') === false

getCountryCallingCode(country)

Returns country calling code for a country. Will throw an error if country doesn't exist or isn't supported by this library.

getCountryCallingCode('RU') === '7'
getCountryCallingCode('IL') === '972'

getExtPrefix(country)

Returns phone number extension prefix for a given country. If no custom ext prefix is defined for a country then the default " ext. " prefix is returned.

getExtPrefix('US') === ' ext. '
getExtPrefix('GB') === ' x'

parseDigits(text)

Parses digits from string. Can be used for building a phone number extension input component (e.g. react-phone-number-input).

parseDigits('x123') === '123'
parseDigits('٤٤٢٣') === '4423'

parseIncompletePhoneNumber(text)

Parses phone number characters (+ and digits). Can be used for building a phone number input component (e.g. react-phone-number-input).

parseIncompletePhoneNumber('8 (800) 555') === '8800555'
parseIncompletePhoneNumber('+7 800 555') === '+7800555'
parseIncompletePhoneNumber('+٤٤٢٣٢٣٢٣٤') === '+442323234'

formatIncompletePhoneNumber(value, country)

Formats incomplete phone number as a national one for a given country. If country is not specified then outputs the phone number in international format. This is just an alias for new AsYouType(country, metadata).input(value). Can be used for building a phone number input component (e.g. react-phone-number-input).

formatIncompletePhoneNumber('8800555', 'RU') === '8 (800) 555'
formatIncompletePhoneNumber('+7800555') === '+7 800 555'

Legacy API

Legacy API (before version 1.6.0): parse(), parseNumber(), format(), formatNumber(), isValidNumber(), getNumberType().

parseNumber(text, [defaultCountry], [options])

(previously called parse())

(legacy API)

Attempts to parse a phone number from text.

If defaultCountry is passed then it's gonna be the default country for parsing non-international phone numbers.

Returns { country, phone, ext } object where

If the phone number supplied isn't valid then an empty object {} is returned.

Examples
// Parses international numbers.
parseNumber('+1 213 373 4253') === { country: 'US', phone: '2133734253' }
parseNumber('Phone: +1-213-373-4253.') === { country: 'US', phone: '2133734253' }
parseNumber('+12133734253') === { country: 'US', phone: '2133734253' }

// Parses national numbers provided a default country.
parseNumber('Phone: (213) 373-4253.', 'US') === { country: 'US', phone: '2133734253' }

// Parses phone number extensions.
parseNumber('(213) 373-4253 ext. 123', 'US') === { country: 'US', phone: '2133734253', ext: '123' }

// Parses RFC 3966 phone number URIs.
parseNumber('tel:+78005553535;ext=123') === { country: 'RU', phone: '8005553535', ext: '123' }

If the phone number supplied isn't valid then an empty object {} is returned.

parseNumber('+1 111 111 1111') === {}
parseNumber('(111) 111-1111', 'US') === {}
parseNumber('abcdefg') === {}

Available options:

  • defaultCountry : string — Same as the defaultCountry argument.

  • extended : boolean — If set to true then parseNumber() will attempt to parse even a remotely hypothetical phone number even if it is considered "invalid".

{ extended: true } documentation and examples

The result of "extended" parsing is an object where

  • country is a country code.
  • phone is a national (significant) number.
  • ext is a phone number extension.
  • countryCallingCode is a country calling code.
  • carrierCodes are only used in Colombia and Brazil and only when dialing within those countries from a mobile phone to a fixed line number.
  • valid: boolean — whether it's a "valid" (real) phone number.
  • possible: boolean — a phone number is considered "possible" when it fits the phone number length rules for a given country. E.g. for US national (significant) number regexp is [2-9]\d{9} and possible national (significant) number length is 10 so a phone number (111) 111-1111 is not a "valid" number because it doesn't match the US national (significant) number regexp but it is a "possible" number because it's 10 digits long.
  • Some or all of these properties may be absent from the result object.
// If the number is valid.
parseNumber('Phone: (213) 373-4253.', 'US', { extended: true }) ===
{
  country: 'US',
  phone: '2133734253',
  ext: undefined,
  countryCallingCode: 1,
  carrierCode: undefined,
  valid: true,
  possible: true
}

// If the number is not "valid" but "possible".
parseNumber('(111) 111-1111', 'US', { extended: true }) ===
{
  country: 'US',
  phone: '1111111111',
  ext: undefined,
  countryCallingCode: 1,
  carrierCode: undefined,
  valid: false,
  possible: true
}

// If the number is not "valid" but "possible"
// and country can't be derived from it.
// (e.g. can't tell if it's a US number or a Canadian number)
parseNumber('+1 111 111 1111', { extended: true }) ===
{
  country: undefined,
  phone: '1111111111',
  ext: undefined,
  countryCallingCode: 1,
  carrierCode: undefined,
  valid: false,
  possible: true
}

// If the number is not "possible" (invalid length).
parseNumber('(213) 373', 'US', { extended: true }) ===
{
  country: 'US',
  phone: '213373',
  ext: undefined,
  countryCallingCode: 1,
  carrierCode: undefined,
  valid: false,
  possible: false
}

// In some cases if the number is extremely not "possible"
// then an empty object `{}` is returned.
//
// Too short (or too long) for any country's phone number.
parseNumber('1', 'US', { extended: true }) === {}
// Non-existent country calling code.
parseNumber('+210', { extended: true }) === {}
// No phone number found.
parseNumber('abcdefg', 'US', { extended: true }) === {}

The "extended" parsing mode is the default behaviour of the original Google's libphonenumber: it still returns parsed data even if the phone number being parsed is not considered valid (but is kinda "possible"). I guess this kind of behaviour is better for crawling websites for phone numbers because when mining "big data" it is better to extract all possible info rather than discard some pieces of it prematurely, e.g. when national (significant) number regexp for some country gets outdated which might very well happen because phone numbering plans are changing constantly around the world. Maybe after all it would make sense to make the "extended" parsing mode the default one in the next major version. I guess it would.

Also parses IDD-prefixed phone numbers

Sometimes users icorrectly input phone numbers in "out-of-country" dialing (IDD-prefixed) format instead of the proper international phone number format (the "+" notation). In such cases parseNumber() will attempt to parse such IDD-prefixed numbers if "default country" is provided:

// International format.
parseNumber('+61 2 3456 7890') === { country: 'AU', phone: '234567890' }
// IDD-prefixed format.
parseNumber('011 61 2 3456 7890', 'US') === { country: 'AU', phone: '234567890' }

formatNumber(number, format, [options])

(previously called format())

(legacy API)

Formats a number into a string according to a format.

Available formats and options are the same as for PhoneNumber.format(format).

The number argument must be either a result of parseNumber() function call (to strip national prefix) or an E.164 phone number string (e.g. +12133734253).

Examples
// Formats E.164 phone numbers.
formatNumber('+12133734253', 'NATIONAL') === '(213) 373-4253'
formatNumber('+12133734253', 'INTERNATIONAL') === '+1 213 373 4253'

// Formats E.164 phone numbers when
// they're not "valid" but still "possible".
formatNumber('+11111111111', 'NATIONAL') === '(111) 111-1111'
formatNumber('+11111111111', 'INTERNATIONAL') === '+1 111 111 1111'

// Formats E.164 phone numbers when
// they're not "valid" and not "possible" (invalid length).
formatNumber('+11111', 'NATIONAL') === '1111'
formatNumber('+11111', 'INTERNATIONAL') === '+1 1111'

// Formats a result of `parseNumber()` function call.
const parsedNumber = parseNumber('2133734253', 'US')
formatNumber(parsedNumber, 'NATIONAL') === '(213) 373-4253'
formatNumber(parsedNumber, 'INTERNATIONAL') === '+1 213 373 4253'

// Formats a result of `parseNumber()` function call in "extended" mode
// when it's not a "valid" number but is still a "possible" one.
const possibleNumber = parseNumber('+11111111111', { extended: true })
formatNumber(possibleNumber, 'NATIONAL') === '(111) 111-1111'
formatNumber(possibleNumber, 'INTERNATIONAL') === '+1 111 111 1111'

// Formats a result of `parseNumber()` function call in "extended" mode
// when it's neither a "valid" number nor a "possible" one (invalid length).
const possibleNumber = parseNumber('+11111', { extended: true })
formatNumber(possibleNumber, 'NATIONAL') === '1111'
formatNumber(possibleNumber, 'INTERNATIONAL') === '+1 1111'

// Formats phone number extensions.
formatNumber({ country: 'US', phone: '2133734253', ext: '123' }, 'NATIONAL') ===  '(213) 373-4253 ext. 123'

// When given an object not having `phone` property
// (e.g. a empty object `{}`) it will throw.
formatNumber({}) throws Error

getNumberType(number, [defaultCountry])

(legacy API)

See the description for PhoneNumber.getType().

The number argument can be either a result of the parseNumber() function call — { country, phone } — or a string (phone number digits only) possibly accompanied with the second defaultCountry argument.

Examples
getNumberType('+79160151539') === 'MOBILE'
getNumberType('9160151539', 'RU') === 'MOBILE'
getNumberType({ phone: '9160151539', country: 'RU' }) === 'MOBILE'

isValidNumber(number, [defaultCountry])

(legacy API)

Checks if a phone number is valid, the validation is more strict than parseNumber().

The number argument can be either a result of the parseNumber() function call — { country, phone } — or a string (phone number digits only) possibly accompanied with the second defaultCountry argument.

Examples
isValidNumber('+12133734253') === true
isValidNumber('+1213373') === false

isValidNumber('2133734253', 'US') === true
isValidNumber('21337', 'US') === false

isValidNumber({ phone: '2133734253', country: 'US' }) === true

The difference between using parseNumber() and isValidNumber()

The difference between using parseNumber() and isValidNumber() for phone number validation is that isValidNumber() also checks the precise regular expressions of possible phone numbers for a country. For example, for Germany parseNumber('123456', 'DE') would return { country: 'DE', phone: '123456' } because this phone number matches the general phone number rules for Germany (basic length check, etc). But, if the metadata is compiled with --extended (or relevant --types) flag (see Customizing metadata section of this document) then isValidNumber() is gonna use those precise regular expressions for extensive validation and isValid('123456', 'DE') will return false because the phone number 123456 doesn't actually exist in Germany.

This is how it is implemented in the original Google's libphonenumber: parseNumber() parses phone numbers and loosely validates them while isValidNumber() validates phone numbers precisely (provided the precise regular expressions are included in metadata).

The precise regular expressions aren't included in the default metadata because that would cause the default metadata to grow twice in its size: the complete ("full") metadata size is about 145 kilobytes while the reduced ("default") metadata size is about 77 kilobytes. Hence in the default configuration isValidNumber() performs absolutely the same "lite" validation as parseNumber(). For enabling extensive phone number validation the simplest way is to import functions from libphonenumber-js/custom module and supply them with libphonenumber-js/metadata.full.json. For generating custom metadata see the instructions provided in the Customizing metadata section of this document.

isValidNumberForRegion()

The optional defaultCountry argument is the default country, i.e. it does not restrict to just that country, e.g. in those cases where several countries share the same phone numbering rules (NANPA, Britain, etc). For example, even though the number 07624 369230 belongs to the Isle of Man ("IM" country code) calling isValidNumber('07624369230', 'GB') still returns true because the country is not restricted to GB, it's just that GB is the default one for the phone numbering rules. For restricting the country see isValidNumberForRegion() though restricting a country might not be a good idea.

// Even though '07624 369230' number belongs to the Isle of Man ("IM")
// the `defaultCountry` argument "GB" still works here because
// "GB" and "IM" both share the same phone numbering rules ("+44").
isValidNumber('07624369230', 'GB') === true
isValidNumber('07624369230', 'IM') === true

// Imposing country restrictions.
isValidNumberForRegion('07624369230', 'GB') === false
isValidNumberForRegion('07624369230', 'IM') === true

Using phone number validation feature

I personally wouldn't rely on strict phone number validation too much because it might get outdated:

  • New phone number rules can be added to Google's libphonenumber library after they have already been implemented in real life (which can introduce a delay).
  • From time to time those new rules from Google's libphonenumber are updated in this library.

  • And then there's still the web application itself using this library and until a developer installs libphonenumber-js@latest manually and redeploys the web application it's gonna use the old (potentially outdated) phone number validation rules which could result in losing customers with perfectly valid (but not yet supported) phone numbers if a website form is too strict about validating user's input.

Phone number validation rules are constantly changing for --extended rules and are fairly static for "general" ones. Still imagine a web application (e.g. a promosite or a "personal website") being deployed once and then running for years without any maintenance.

React

There's also a React component utilizing this library — react-phone-number-input (or without country select).

Bug reporting

When reporting an issue one must also provide a link to Google's libphonenumber demo page illustrating the expected behaviour. This includes validation, parsing, formatting and "as you type" formatting. For example, for an Australian number 438 331 999 Google's demo outputs four sections — "Parsing Result", "Validation Results", "Formatting Results" and "AsYouTypeFormatter Results". In a bug report, first describe the observed libphonenumber-js demo result and then Google's demo result (with a link to it) which must differ from the observed libphonenumber-js demo result. If the observed libphonenumber-js demo result is the same as Google's demo result and you don't agree with Google's demo result then create an issue in Google's repo.

When reporting findNumbers() bugs one should know that findNumbers() code was ported from Google's Java code. I didn't write it myself, I just ported it. Therefore, it is unlikely that anyone other than Google will be fixing such bugs.

TypeScript

TypeScript support for this library is entirely community-driven. I myself don't use TypeScript. Send your pull requests.

CDN

One can use any npm CDN service, e.g. unpkg.com or jsdelivr.net

<script src="https://unpkg.com/libphonenumber-js@[version]/bundle/libphonenumber-[type].js"></script>

<script>
  alert(new libphonenumber.AsYouType('US').input('213-373-4253'))
</script>

where [version] is an npm package version range (for example, 1.x or ^1.7.6) and [type] is the bundle type: min, max or mobile.

Metadata

Metadata is generated from Google's original PhoneNumberMetadata.xml by transforming XML into JSON and removing unnecessary fields.

The metadata update process is automated through an "autoupdate" script (see ./autoupdate.sh or ./autoupdate.cmd). The script detects changes to PhoneNumberMetadata.xml in Google libphonenumber's repo and if there are changes then it pulls the latest metadata, processes it, commits the changes to GitHub, builds a new version of the library and releases it to NPM. So this library's metadata is supposed to be up-to-date. I could set up this script to run automatically but on my Windows machine ssh-agent doesn't work properly so I run the "autoupdate" script manually from time to time.

Customizing metadata

This library comes prepackaged with three flavors of metadata:

  • metadata.full.json — contains everything, including all regular expressions for precise phone number validation and getting phone number type, but is about 140 kilobytes in size.
  • metadata.min.json — (default) the minimal one, doesn't contain regular expressions for precise phone number validation and getting phone number type for most countries, is about 75 kilobytes in size.
  • metadata.mobile.json — is the "full" metadata which only supports mobile numbers, is about 105 kilobytes in size.

Sometimes, if only a specific set of countries is needed in a project, and a developer really wants to reduce the resulting bundle size, say, by 50 kilobytes (even when including all regular expressions for precise phone number validation and getting phone number type), then they can generate such custom metadata and pass it as the last argument to this library's "core" (used to be called "custom") functions.

See generate custom metadata instructions.

First, add metadata generation script to your project's package.json

{
  "scripts": {
    "libphonenumber-metadata": "libphonenumber-generate-metadata metadata.custom.json --countries RU,DE --extended",
  }
}

And then run it like npm run libphonenumber-metadata.

The arguments are:

  • The first argument is the output metadata file path.
  • --countries argument is a comma-separated list of the countries included (if --countries is omitted then all countries are included).
  • --extended argument may be passed to include all regular expressions for precise phone number validation and getting phone number type, which will enlarge the resulting metadata size approximately twice.
  • --types ... argument may be passed instead of --extended to generate metadata that only supports the selected phone number types (a comma-separated list, e.g. --types mobile,fixed_line). See the list of all possible phone number types.

Then use the generated metadata.custom.json file with the "core" functions.

Pass the metadata argument as the last one to the "core" functions.

In ES6 that would be:

import {
  parsePhoneNumberFromString as _parsePhoneNumberFromString,
  findNumbers as _findNumbers,
  AsYouType as _AsYouType
} from 'libphonenumber-js/core'

import metadata from 'libphonenumber-js/metadata.full.json'

function call(func, _arguments) {
  var args = Array.prototype.slice.call(_arguments)
  args.push(metadata)
  return func.apply(this, args)
}

export function parsePhoneNumberFromString() {
  return call(_parsePhoneNumberFromString, arguments)
}

export function findNumbers() {
  return call(_findNumbers, arguments)
}

export function AsYouType(country) {
  return _AsYouType.call(this, country, metadata)
}
AsYouType.prototype = Object.create(_AsYouType.prototype, {})
AsYouType.prototype.constructor = AsYouType

And for Common.js environment that would be:

var core = require('libphonenumber-js/core')
var metadata = require(libphonenumber-js/metadata.full.json)

function call(func, _arguments) {
  var args = Array.prototype.slice.call(_arguments)
  args.push(metadata)
  return func.apply(this, args)
}

exports.parsePhoneNumberFromString = function parsePhoneNumberFromString() {
  return call(core.parsePhoneNumberFromString, arguments)
}

exports.findNumbers = function findNumbers() {
  return call(core.findNumbers, arguments)
}

exports.AsYouType = function AsYouType(country) {
  return core.AsYouType.call(this, country, metadata)
}
exports.AsYouType.prototype = Object.create(core.AsYouType.prototype, {})
exports.AsYouType.prototype.constructor = exports.AsYouType

Legacy: How to use the generated metadata.custom.json file with the legacy "custom" functions.

Pass the metadata argument as the last one to the "custom" functions.

In ES6 that would be:

import {
  parseNumber,
  formatNumber,
  isValidNumber,
  getNumberType,
  AsYouType
} from 'libphonenumber-js/custom'

import metadata from 'libphonenumber-js/metadata.full.json'

parseNumber('+78005553535', metadata)
formatNumber({ phone: '8005553535', country: 'RU' }, metadata)
isValidNumber('+78005553535', metadata)
getNumberType('+78005553535', metadata)
new AsYouType('RU', metadata).input('+78005553535')

And for Common.js environment that would be:

var custom = require('libphonenumber-js/custom')
var metadata = require(libphonenumber-js/metadata.full.json)

exports.parseNumber = function parseNumber() {
  var parameters = Array.prototype.slice.call(arguments)
  parameters.push(metadata)
  return custom.parseNumber.apply(this, parameters)
}

exports.formatNumber = function formatNumber() {
  var parameters = Array.prototype.slice.call(arguments)
  parameters.push(metadata)
  return custom.formatNumber.apply(this, parameters)
}

exports.isValidNumber = function isValidNumber() {
  var parameters = Array.prototype.slice.call(arguments)
  parameters.push(metadata)
  return custom.isValidNumber.apply(this, parameters)
}

exports.getNumberType = function isValidNumber() {
  var parameters = Array.prototype.slice.call(arguments)
  parameters.push(metadata)
  return custom.getNumberType.apply(this, parameters)
}

exports.AsYouType = function AsYouType(country) {
  custom.AsYouType.call(this, country, metadata)
}

exports.AsYouType.prototype = Object.create(custom.AsYouType.prototype, {})
exports.AsYouType.prototype.constructor = exports.AsYouType

Metadata should be re-generated each time the project is being deployed because Google constantly updates their metadata.

Maintenance

Google periodically releases new metadata with the changes described in the release notes. Sometimes those are minor non-breaking updates, sometimes those are major-version breaking updates. The metadata should be periodically updated via autoupdate.cmd (Windows) and autoupdate.sh (Linux/macOS) scripts. Also Google sometimes (very rarely) updates their code: phonenumberutil.js (parseNumber(), formatNumber(), isValidNumber(), getNumberType()), asyoutypeformatter.js (AsYouType), PhoneNumberMatcher (findNumbers()). The latest sync-up was performed on October 18th, 2018.

Contributing

After cloning this repo, ensure dependencies are installed by running:

npm install

This module is written in ES6 and uses Babel for ES5 transpilation. Widely consumable JavaScript can be produced by running:

npm run build

Once npm run build has run, you may import or require() directly from node.

After developing, the full test suite can be evaluated by running:

npm test

Test coverage must remain at 100%:

npm run test-coverage

When you're ready to test your new functionality on a real project, you can run

npm pack

It will build, test and then create a .tgz archive which you can then install in your project folder

npm install [module name with version].tar.gz

Advertisement

If you're looking for an international "2 days ago" javascript solution then check out javascript-time-ago.

License

MIT

About

A simpler (and smaller) rewrite of Google Android's libphonenumber library

Resources

License

MIT, Apache-2.0 licenses found

Licenses found

MIT
LICENSE
Apache-2.0
LICENSE.Apache

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 99.9%
  • Other 0.1%