Using libphonenumber for International Phone Numbers

Google's open-source libphonenumber library is great for parsing, formatting, and validating international phone numbers. Here's how to use the library's JavaScript version in your multilingual web applications.

Phone numbers vary in form and are handled differently from country to country. Google’s open-source libphonenumber library can help us properly format phone numbers along with country codes and also validate input fields in web applications.

Google has its own Clojure-based JavaScript version of the library, but we’ll be using another library, catamphetamine/libphonenumber-js, which is a simpler and smaller rewrite of Google’s libphonenumber in JavaScript. This particular library can be integrated into any preferred framework.

For this tutorial, we’ll build a framework-agnostic demo app so it can be useful for as many developers as possible.

🗒 Note » Get the code for the demo app in our GitHub repo.

Basic setup and installation

Let’s create a project and two separate files: index.html and app.js.

For simplicity, we’ll be using the CDN version of the library. The library comes in multiple variations, such as min, max, mobile, so choose the one that best suits your requirements.

  • max — the complete metadata set is about 145 kilobytes in size (libphonenumber-js/metadata.full.json); go for this one when you need the most strict version of isValid() or if you need to detect the phone number type (“landline”, “mobile”, etc).
  • min — the smallest metadata set, about 80 kilobytes in size (libphonenumber-js/metadata.min.json); it gets chosen by default when you don’t need to detect the phone number type or when a basic version of isValid() is enough; the min metadata set doesn’t contain the regular expressions for phone number digits validation (via .isValid()) and detecting phone number type (via .getType()) for most countries; in this case, .isValid() still performs some basic phone number validation (e.g. checks phone number length), but it doesn’t validate phone number digits themselves the way max metadata validation does.
  • mobile — the complete metadata set for dealing with mobile numbers only, about 95 kilobytes in size (libphonenumber-js/metadata.mobile.json); make use of it when you need max metadata and when you only accept mobile numbers; other phone number types will still be parseable, but they won’t be recognized as being “valid” (.isValid() will return false).

In this tutorial, we’ll use the max variation as it provides all features available.

// CDN format
https://unpkg.com/libphonenumber-js@[version]/bundle/libphonenumber-[variation].js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  < script src="https://unpkg.com/libphonenumber-js@1.9.6/bundle/libphonenumber-max.js"></script>
  < script src="app.js"></script>
  <title>Demo</title>
</head>
</html>

That’s it, our setup is complete. All the library’s methods are exposed on the global libphonenumber  object. We can now start writing the code in the app.js file and use our good old console to see the results in action.

Parsing a phone number

Let’s see how we can use the parsePhoneNumber() method in some scenarios. First, let’s pass the full phone number including the country code.

libphonenumber.parsePhoneNumber('+919098765432')

The method returns the data below. The library can accurately detect the country, country calling code, and the number without the country calling code.

{ 
 country: "IN",
 countryCallingCode: "91", 
 nationalNumber: "9098765432", 
 number: "+919098765432" 
}

parsePhoneNumber() also takes the country code (a two-character alphanumeric code) as the second parameter. When we are sending the country code, we can pass the number without the country code as well.

libphonenumber.parsePhoneNumber('9098765432', 'IN')

The output would be exactly the same; the method also returns some useful functions that we’ll go through in detail. Some important methods include:

  1. format()
  2. formatInternational()
  3. formatNational()
  4. getType()
  5. isEqual()
  6. getURI()
  7. isNonGeographic()
  8. isPossible()
  9. isValid()

1. format(format: string, [options]): string

This method takes two arguments: the formatted type and a few additional arguments (see the example below).

let numberObj = libphonenumber.parsePhoneNumber('9098765432', 'IN')
numberObj.format('NATIONAL')
numberObj.format('INTERNATIONAL')
numberObj.format('RFC3966')
numberObj.format('IDD', {fromCountry: 'US'})
numberObj.format('NATIONAL', {nationalPrefix: false})
090987 65432 // national format (with national prefix)
+91 90987 65432  // international format
tel:+919098765432 // text that can be used for html anchor tags for call function
011 91 90987 65432 // Out of country dialing format (fromCountry option is mandatory)
90987 65432 // National number without national prefix

2. formatInternational(): string

This method is an alias of format('INTERNATIONAL') and returns the same output.

3. formatNational(): string

An alias of format('NATIONAL') and returns the same output.

4. getType(): string?

GetType will return the type of number (e.g. landline, mobile, toll-free, etc) and undefined if the number is invalid or not available for that country. Here’s an example:

let numberObj1 = libphonenumber.parsePhoneNumber('9098765432', 'IN')
let numberObj2 = libphonenumber.parsePhoneNumber('08212345654', 'IN')
let numberObj3 = libphonenumber.parsePhoneNumber('+80030009009')
numberObj1.getType()
numberObj2.getType()
numberObj3.getType()
MOBILE
FIXED_LINE
TOLL_FREE

5. isEqual({number}): boolean

isEqual will return true if the number passed matches, else returns false; this method just does normal string comparison with the international format.

numberObj1.isEqual({number:"+919098765432"}) // true

6. getURI(): string?

This method is an alias of format('RFC3966') and returns the same output.

7. isNonGeographic(): boolean

Returns true if the number belongs to a non-geographic numbering plan.

let numberObj1 = libphonenumber.parsePhoneNumber('9098765432', 'IN') 
let numberObj2 = libphonenumber.parsePhoneNumber('08212345654', 'IN') 
let numberObj3 = libphonenumber.parsePhoneNumber('+80030009009') 

numberObj1.isNonGeographic() // false
numberObj2.isNonGeographic() // false
numberObj3.isNonGeographic() // true

There are several codes that don’t belong to any country, e.g. +800, +808, +870, etc. Such phone numbering plans are called “non-geographic”, and their phone numbers have country set to undefined.

8. isPossible(): boolean

This method checks if the phone number is “possible”. The result is purely based on the number length.

let numberObj1 = libphonenumber.parsePhoneNumber('1098765432', 'IN')
let numberObj2 = libphonenumber.parsePhoneNumber('0821564', 'IN')

numberObj1.isPossible() // true
numberObj2.isPossible() // false

9. isValid(): boolean

This method first checks isPossible(). If that’s true, it then checks the phone numbers with regular expressions to see if the number is actually valid. Let’s use the numbers from our previous example:

let numberObj1 = libphonenumber.parsePhoneNumber('1098765432', 'IN')
let numberObj2 = libphonenumber.parsePhoneNumber('0821564', 'IN')

numberObj1.isValid() // false
numberObj2.isValid() // false

As we can see, the number 1098765432, which was possible, is not actually a valid phone number.

Parsing and formatting incomplete phone numbers

parseIncompletePhoneNumber() and formatIncompletePhoneNumber(), as the names suggest, are used when we deal with incomplete phone numbers; both are mainly used while building other libraries on top of libphonenumber

libphonenumber.parseIncompletePhoneNumber('8 (800) 555')
libphonenumber.parseIncompletePhoneNumber('+7 800 555')
libphonenumber.formatIncompletePhoneNumber('8800555', 'RU')
libphonenumber.formatIncompletePhoneNumber('+7800555')
8800555
+7800555
8 (800) 555
+7 800 555

As You Type Formatter

asYouType() is mostly used to format/parse a partial number as soon as the user starts typing in an input field. Since the formatting is done on the fly, it’s important to pass the 2-digit alphabet country code for it to properly format if we’re not entering the country code along with the number. Check the example below:

const asYouType = new libphonenumber.AsYouType('US')
asYouType.input('2133734')
asYouType.getChars()
asYouType.getTemplate()
// output
(213) 373-4
2133734
(xxx) xxx-x

Finding phone numbers in text blocks

findPhoneNumbersInText() can be used to find phone numbers that are present in a block of text. The method takes the string of text as a first argument and an optional country parameter. If the number in the text block has a numeric country code already appended, we can skip the optional parameter, else it’s required to pass the country code for the method to work.

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

This method returns an array of objects where each object has details of the number found in the text.

[{
  endsAt: 41,
  startsAt: 23,
  number: {
    country: "RU",
    countryCallingCode: "7",
    metadata: {...},
    nationalNumber: "8005553535",
    number: "+78005553535",
  }
},
{
  endsAt: 112,
  startsAt: 88,
  number: {
    country: "US",
    countryCallingCode: "1",
    ext: "1234",
    metadata: {...},
    nationalNumber: "2133734253",
    number: "+12133734253",
  }
}]

isSupportedCountry(country: string): boolean

We can use the isSupportedCountry() method to check if the library supports a particular country. The method takes the 2-character country code as an argument and returns a boolean value:

libphonenumber.isSupportedCountry('US') // true

getCountries(): string[]

To fetch the list of countries supported by the library, we can use the getCountries() method:

libphonenumber.getCountries()

getExtPrefix(country: string): string

Extensions are basically 4-digit numbers that are used to route to a particular user/service among a group of users/services sharing the same main phone number.

For better readability, these extensions are generally prefixed with specific characters. To get the extension prefix of a country, we can use the getExPrefix() method:

libphonenumber.getExtPrefix('US')
libphonenumber.getExtPrefix('GB')
libphonenumber.getExtPrefix('IN')
ext prefix of US  ext. 
ext prefix of UK  x
ext prefix of AU  ext.

getExampleNumber(country: string, examples: object): PhoneNumber

The library also provides a method to get a sample valid phone number of any supported country. The examples aren’t included in the main library, so we need to import a JSON file that has all the example phone numbers. Follow the example below:

fetch('https://unpkg.com/libphonenumber-js@1.9.6/examples.mobile.json')
  .then(response => response.json())
  .then(examples => {
     libphonenumber.getExampleNumber('US', examples)
   }
  }
};

We’ll get the following response:

country: "US"
countryCallingCode: "1"
nationalNumber: "2015550123"
number: "+12015550123"

Metadata

The libphonenumber library also provides useful metadata like 2-letter country codes, non-geographic phone number codes, codes associated with different countries etc. This metadata can be accessed programmatically by using the exported Metadata class.

libphonenumber.Metadata()
{
  metadata: {
   countries: {…},
   country_calling_codes: {…},
   nonGeographic:{…},
  }
}

Wrapping it up

The libphonenumber library is Google’s ultimate formatting, parsing, and validation tool for international phone numbers. By employing this open-source library, we can avoid paid APIs that provide phone number validation. The library makes it a lot easier to format phone numbers and make them more readable to users across the globe. The library’s JavaScript version, which we used in this tutorial, is highly efficient and can be used in multiple framework-specific plugins such as react-phone-number-input or vue-phone-number-input.

5 (100%) 71 votes
Comments
close

Automate Your Localization Workflow for Continuous Deployment

Automate Localization for Continuous Deployment

  • Integrate Phrase into your agile environment easily
  • Import and export your localization files in any format
  • Automate your localization workflow to speed up every release