JavaScript
Article

Working with Phone Numbers in JavaScript

By Lukas White

When you’re collecting data from users, there are two key challenges; collecting that information, and validating it. Some types of information are straightforward – someone’s age, for example, couldn’t really be simpler to collect and to validate. Names aren’t as straightforward as they sound, but provided you cater for edge cases and international variations – for example patronymics, the mononymous, or even just people with hyphenated surnames – you can’t go too far wrong (although plenty of applications and services do!). Email addresses, while theoretically very easy to validate, have their own challenges – yet nevertheless, there are plenty of regular expressions in the wild that aren’t quite right.

And then there are telephone numbers. These are hard. Really hard. In this article I’ll discuss some of the challenges around collecting, validating, and displaying telephone numbers.

Why Telephone Numbers are Different

Perhaps you’re thinking that since telephone numbers tend to follow a pretty rigid format, such as this:

202-456-1111

…that it ought to be simple to construct a simple regular expression to validate them. In fact, here’s one:

^(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}$

Well, stop right there. For starters, here are just some variations of the number above, all of which are perfectly valid:

202 456 1111
(202) 456 1111
2024561111
1-202-456-1111
1-202-456-1111 x1234
1-202-456-1111 ext1234
1 (202) 456-1111
1.202.456.1111
1/202/456/1111
12024561111
+1 202 456 1111

So based on that, we know that the regular expression apparoach isn’t as simple as we first thought – but that’s only the half of it. These examples are just for a US-based number. Sure, if you know that the number you’re collecting is going to be for a specific country, you may be able to use a regular expression. Otherwise, this approach won’t cut it.

Let’s look at some of the other issues around telephone numbers, and why they make our job even harder.

Numbers Change

All sorts of external factors can have implications for telephone numbering. Whole countries come and go, introducing new country prefixes. New classifications of numbers introduce new numbering systems – premium-rate, local-rate, toll-free, and so on. When a carrier runs out of one set of numbers – like, sadly, premium-rate – they simply introduce a new prefix.

Some changes have enormous implications; in the United Kingdom some years ago, for example, the entire regional numbering system underwent a drastic change, with virtually every area code getting an additional “1” inserted. Even then, the capital had a subtly different system. It was probably a decade before signage was changed across the country to reflect the changes.

Then, of course, there was the enormous and unprecedented growth in mobile. No longer was the number of telephone numbers required largely limited to the number of households, but many times over. The continued strain on the pool of available numbers can only increase the likelihood of further changes.

International Dialing Codes

It’s often important to capture a number’s international dialing code. In some cases, the context might mean they aren’t required. For example if you operate in a single country, and telephone numbers are captured to be used by a human operator, you might not need them. But for anything remotely automated – such as sending SMS messages – or to validate them effectively, you’ll need to capture the country prefix.

The countries library contains a bunch of geographical information which includes international dialing codes. Here is an excerpt from countries.json from that library:

{
    "name": {
        "common": "Austria",
        "official": "Republic of Austria",
        // ... //
    },
    // ... //
    "callingCode": ["43"],
    // ... //
},

As you can see, this demonstrates that Austria uses the international dialing code 43.

So how might we use this information? Well, using the magic of Lodash (or Underscore), there are a few ways in which we can query dialing code-related information.

For example, to find out whether a given dialing code is valid:

var _ = require('lodash')
    , data = require('world-countries')

module.exports = {
    /**
     * Determines whether a given international dialing code is valid
     * 
     * @param  string  code
     * @return bool
     */
    isValid : function(code) {

        var codes = _.flatten(_.pluck(data, 'callingCode'));

        return _.contains(codes, code);

    }
    // ...
}

There are a more efficent ways of doing this, of course, so this and the following examples aren’t necessarily optimized for production.

We can look up the countries which use a particular dialing code:

/**
 * Gets a list of countries with the specified dialing code
 * 
 * @param  string   code
 * @return array        An array of two-character country codes
 */
getCountries : function(code) {

    var countryEntries = _.filter(data, function(country){
        return (_.contains(country.callingCode, code));
    })

    return _.pluck(countryEntries, 'cca2');

}

Finally, we can get the dialing codes for given country:

/**
 * Gets the dialing codes for a given country
 * 
 * @param  string   country     The two-character country code
 * @return array        An array of strings representing the dialing codes
 */
getCodes : function(country) {
        
    // Get the country entry
    var countryData = _.find(data, function(entry) {
        return (entry.cca2 == country);
    });

    // Return the code(s)
    return countryData.callingCode;

}

You’ll find these functions packaged up as a module, along with unit tests, in the repository that accompanies the article.

Even international dialing codes, however, aren’t as straightforward as you may think. The format can vary – 1, 43, 962 1868 are all valid codes. There isn’t necessarily a one-to-one mapping; 44 for example, is used not just for the United Kingdom but for the Isle of Man, Guernsey and Jersey.

Numbers must also be altered according to where you’re dialing from. From abroad, to call a UK number you need to drop the leading zero and prefix with the dialing code 44:

020 7925 0918

…becomes…

+44 20 7925 0918

You can also replace the “+” with a double zero:

0044 20 7925 0918

To complicate things even further, some numbers vary when called from outside of a country depending on which country you’re dialing from. In the US, for example, numbers must also be prefixed with the US exit code 011, so the example above becomes:

011 44 20 7925 0918

Thankfully, there is a format we can use which enable us to get around these variations.

E.164

Luckily for developers there is an unambiguous, internationally recognized standard for telephone numbers anywhere in the World called E.164. The format is broken down as follows:

  • A telephone number can have a maximum of 15 digits
  • The first part of the telephone number is the country code
  • The second part is the national destination code (NDC)
  • The last part is the subscriber number (SN)
  • The NDC and SN together are collectively called the national (significant) number

(source)

Here’s the number from earlier, in E.164 format:

+12024561111

We can use the same format for, as an example, a London-based UK number:

+442079250918

We can represent any valid telephone number using the E.164 format. We know what country it refers to, and it’s unabmiguous – making it the ideal choice for storage. It’s also commonly used for telephony based services such as SMS providers, as we’ll see a little later.

There’s a catch, of course. The E.164 standard might be great for storage, but terrible for two things. First, virtually no one would type or read out their number in that format. Second, it’s hopeless in terms of its readability. Later though, when we look at libphonenumber, we’ll see that there are ways of formatting numbers for humans.

Collecting Telephone Numbers

First though, let’s look at the issue of collecting telephone numbers.

HTML5 and the “tel” input

HTML5 introduced a new “tel” input type. However, because of the issues around the variations in format, it doesn’t actually place any restrictions on what the user can type, nor does it perform any validation in the same way as, say, the email element. Nevertheless, there are some advantages – when used on a mobile site a user’s telephone keypad will usually be displayed, rather than a conventional keyboard layout.

You can use a single element to collect a number:

<input type="tel" name="number">

Alternatively, you can break a number down into separate elements:

<!-- area code and number -->
<input type="tel" name="number">

<!-- country code, area code and number -->
<input type="tel" name="country" size="4"> <input type="tel" name="area" size="6"> <input type="tel" name="number" size="8">

<!-- US-style -->
(<input type="tel" size="3">) <input type="tel" size="3"> - <input type="tel" size="4">

Browser support is pretty good (e.g. Chrome 6+, Firefox 4+, Safari 5+, IE 10+), but even in an older browser it will simply fall back to a plain old text field.

Should we decide that a regular expression is sufficient – and remember, there are issues – then we can use the pattern attribute to add some validation:

<input type="tel" name="number" pattern="^(?:\(\d{3}\)|\d{3})[- ]?\d{3}[- ]?\d{4}$">

Masked Inputs

Masked inputs are a common technique for restricting user input or providing hints as to the expected format. But again, unless you can be confident that numbers will always be for a particular country, it’s very difficult to cater to international variations. However, it’s one thing to annoy users by making assumptions – asking a non-US user to provide a state and a zip-code. It’s quite another to make a form completely unusable, for example by forcing people to provide numbers in a certain country’s format.

Nevertheless, they can be effective if you know that certain numbers will be within a particular range. Here is an example of a masked input for US telephone numbers.

A Better Way

There is a better and more flexible way to collect telephone numbers, in the form of an excellent jQuery plugin. It’s illustrated below.

The plugin in action

You can also play with a live demo here.

Usage is simple – make sure you’ve included jQuery, the library, and the CSS file, and that the flag sprite is available and properly referenced from the CSS – you’ll find it in build/img/flags.png.

Next, create an element:

<input type="tel" id="number">

Finally, intialize it as follows:

$("#number").intlTelInput();

For a full list of configuration options, consult the documentation. Later, we’ll look at the utilsScript option, but first, we need to delve into another useful library.

Introducing libphonenumber

Luckily, there’s a solution to many of our validation and formatting woes. Originally developed for the Android operating system, Google’s libphonenumber library offers all sorts of methods and utilities for working with telephone numbers. Better still, it’s been ported from Java to Javascript, so we can use it in web or Node.js applications.

Installation

You can download the library from the project homepage on – as you might expect – Google Code.

You can also get it via npm. Here’s the project page, and to install from the command-line:

npm install google-libphonenumber

You can also install it using Bower:

bower install libphonenumber

If you’re thinking of using it in a front-end project, be warned though – even when minified and compressed, it comes in at over 200Kb.

Parsing Numbers

In order to demonstrate the library’s key features, I’m going to assume you’re writing a Node.js application. You can find some example code in the repository which complements this article.

First, import phoneUtil:

var phoneUtil = require('google-libphonenumber').phoneUtil;

Now you can use its parse() method to interpret a telephone number:

var tel = phoneUtil.parse('+12024561111');

There are a number of things we can do with this. Let’s first import some constants from the library. Change your require declaration to the following:

var phoneUtil = require('google-libphonenumber').phoneUtil
        , PNF = require('google-libphonenumber').PhoneNumberFormat
        , PNT = require('google-libphonenumber').PhoneNumberType;

Now we can do the following:

var tel = phoneUtil.parse('+12024561111');

console.log(phoneUtil.format(tel, PNF.INTERNATIONAL));
console.log(phoneUtil.format(tel, PNF.NATIONAL));
console.log(phoneUtil.format(tel, PNF.E164));

The output from this will be as follows:

+1 202-456-1111
(202) 456-1111
+12024561111

Now try parsing the number without the international dialing code:

var tel = phoneUtil.parse('2024561111');

This will throw the following exception:

Error: Invalid country calling code

This is because without explictly telling it what country the number is for, it’s impossible to interpret. The parse() method takes an optional second parameter, which is the ISO 3166-1 alpha-2 (i.e., two character) country code.

If you try the line again, but this time passing “US” as the second argument, you’ll find that the results are as before:

var tel = phoneUtil.parse('2024561111', 'US');

You can also play around with the formats; all of these will work, too:

var tel = phoneUtil.parse('202-456-1111', 'US');
var tel = phoneUtil.parse('(202) 456 1111', 'US');

To interpret a United Kingdom number:

var tel = phoneUtil.parse('(0) 20 7925 0918', 'GB');
console.log(phoneUtil.format(tel, PNF.INTERNATIONAL));
console.log(phoneUtil.format(tel, PNF.NATIONAL));
console.log(phoneUtil.format(tel, PNF.E164));

This will output the following:

+44 20 7925 0918
020 7925 0918
+442079250918

Once you’ve parsed a number, you can validate it – as we’ll see in the next section.

Validating a Number

Validation follows a similar pattern; again, there is a second optional argument, but one which you’re going to need if the country isn’t implictly stated.

Here are some examples of valid numbers, where the country code is either provided as the second argument, or contained within the first argument:

console.log(phoneUtil.isValidNumber(phoneUtil.parse('+12024561111')));
// => outputs true

console.log(phoneUtil.isValidNumber(phoneUtil.parse('202-456-1111', 'US')));
// => outputs true

console.log(phoneUtil.isValidNumber(phoneUtil.parse('(0) 20 7925 0918', 'GB')));
// => outputs true

If you don’t supply the country code, or it’s not implied, you get the same error as before:

console.log(phoneUtil.isValidNumber(phoneUtil.parse('(0) 20 7925 0918')));
// => throws exception "Error: Invalid country calling code"

console.log(phoneUtil.isValidNumber(phoneUtil.parse('2024561111')));
// => throws exception "Error: Invalid country calling code"

Here are some examples where validation fails, returning false:

console.log(phoneUtil.isValidNumber(phoneUtil.parse('573 1234 1234', 'US')));
// => outputs false

console.log(phoneUtil.isValidNumber(phoneUtil.parse('555-555-5555', 'US')));
// => outputs false (this is often used as a placeholder, but it's not a valid number)

console.log(phoneUtil.isValidNumber(phoneUtil.parse('295-123-1234', 'US')));
// => outputs false (there is no 295 area code in the US)

Be warned, however, as an invalid number can throw an exception:

console.log(phoneUtil.isValidNumber(phoneUtil.parse('NOT-A-NUMBER', 'US')));
// => throws exception "Error: The string supplied did not seem to be a phone number"

Determining a Number’s Type

Sometimes, it’s useful to know the type of a telephone number. For example, you may wish to ensure that you’ve been provided with a mobile number – perhaps you plan to send SMS messages, for example to implement two-factor authentication – or attempt to weed out premium rate numbers.

The library’s getNumberType() function does just that. Let’s take a look.

The function takes a parsed telephone number as its argument:

var tel = phoneUtil.parse('(0) 20 7925 0918', 'GB');
var type = phoneUtil.getNumberType(tel)

The return value is a constant defined in the PhoneNumberType sub-module – you’ll recall we’ve require‘d this in as PNF.

As an example, let’s query whether the number in question is a mobile or a fixed line:

if (type === PNT.MOBILE) {
    console.log("It's a mobile number");
} else if (type === PNT.FIXED_LINE) {
    console.log("It's a fixed line");
}

As seems to be the theme of the topic, naturally there’s a catch. Sometimes, even the libphonenumber library can’t be sure. US numbers, for example, cannot be easily distinguished; hence the constant PNT.FIXED_LINE_OR_MOBILE.

We’ll just have to change our example code to reflect this uncertainty:

if (type === PNT.MOBILE) {
    console.log("It's a mobile number");
} else if (type === PNT.FIXED_LINE) {
    console.log("It's a fixed line");
} else if (type === PNT.FIXED_LINE_OR_MOBILE) {
    console.log("Your guess is as good as mine");
}

There are a number of other possibilities, too. Here is the full list currently:

  • PNT.FIXED_LINE
  • PNT.MOBILE
  • PNT.FIXED_LINE_OR_MOBILE
  • PNT.TOLL_FREE
  • PNT.PREMIUM_RATE
  • PNT.SHARED_COST
  • PNT.VOIP
  • PNT.PERSONAL_NUMBER
  • PNT.PAGER
  • PNT.UAN
  • PNT.UNKNOWN

As you can see, the PNT.UNKNOWN reflects the fact that we can’t necessarily glean any information with any certaintly. So in summary, whilst this feature can be useful as a quick initial check, we can’t rely on it.

Is the Number in Service?

There are plenty of telephone numbers which will validate, but which are not in use. They may have been disconnected, not yet allocated, or perhaps a SIM card has been dropped down a toilet.

If you need to ensure that a number is not just valid but also active, there are a number of options open to you.

One approach is to require users’ confirm their number, in much the same way as you might require users confirm their email address. You can use a service such as Twilio to send an SMS, or even place a call.

Here is a very simple code snippet for generating and sending a confirmation code by SMS using Twilio:

// You'll need to install the Twilio library with "npm install twilio"
var client = require('twilio')('YOUR-SID', 'YOUR-AUTH-TOKEN');

// Generate a random four-digit code
var code = Math.floor(Math.random()*8999+1000); 

// Send the SMS
client.sendMessage({
    to: phoneUtil.format(tel, PNF.E164),  // using libphonenumber to convert to E.164
    from: 'YOUR-NUMBER',
    body: 'Your confirmation code is ' + code
}, function(err, respons) {
    // ...do something
});

It’s then a trivial exercise to ask users to enter the code into a form in your web app to verify it – or you could even allow people to validate their number by replying to the message.

There are also (paid) services which will check whether a number is in service for you in realtime, such as this one from Byteplant.

Other Issues

As with any personal information, there are also plenty of legal issues to be mindful of. In the UK, for example, the Telephone Preference Service (TPS) is a national register of telephone numbers which have explictly been registered by people not wishing to receive marketing communications. There are paid services which offer APIs to check a number against this register, such as this one.

Usability Considerations

It’s very common to request up to three different telephone numbers in a single form; for example daytime, evening and mobile.

It’s also worth remembering that asking for telephone numbers over the internet can come across as rather intrusive. If someone is unwilling to provide that information despite you having made it a required field, they will probably do one of two things:

  • Attempt to “fool” the validation. Depending on the approach, they may type something like “ex directory”, or enter an invalid number – such as one which contains only numbers.
  • Walk away.

Combining the jQuery Plugin with libphonenumber

You might remember that the jQuery plugin has a rather cryptically named option called utilsScript.

This option allows us to take advantage of the validation and formatting features of libphonenumber. Having selected a country – either using the drop-down or by typing the dialing code – it will transform the textfield into a masked input which reflects that country’s numbering format.

The plugin contains a packaged version of libphonenumber; pass the path to this file to the constructor as follows:

$("#number").intlTelInput(
    {
        utilsScript : '/bower_components/intl-tel-input/lib/libphonenumber/build/utils.js'
    }
);

As I’ve previously mentioned, do bear in mind that this approach should be used with caution, owing to the file size of the libphonenumber library. Referencing it here in the constructor does however mean that it can be loaded on demand.

Displaying Telephone Numbers

We’ve looked at how we can format numbers when displaying them to be more “friendly”, using formats such as PNF.INTERNATIONAL and PNF.NATIONAL.

We can also use the tel and callto protocols to add hyperlinks to telephone numbers, which are particulary useful on mobile sites – allowing users to dial a number direct from a web page.

To do this, we need the E.164 format for the link itself – for example:

<a href="tel:+12024561111">+1 202-456-1111</a>

Of course, you could use the libphonenumber library’s format() method to render both the E.164 version (PNF.E164) and the more user-friendly display version.

Microdata

We can also use Microdata to mark up telephone numbers semantically. Here’s an example; note the use of itemprop="telephone" to mark-up the link:

<div itemscope itemtype="http://schema.org/LocalBusiness">
    <h1 itemprop="name">Acme Corp, Inc.</h1>
    Phone: <span itemprop="telephone"><a href="tel:+12024561111">202-456-1111</a></span>
</div>

Summary

In this article, we’ve opened up the hornets nest that is telephone numbers. It should be pretty apparent by now that there are all sorts of complexities, subtleties and gotchas you need to be aware of if you need to collect, validate and display them.

We’ve looked at a few methods for collecting numbers – the “tel” input type, masked inputs and finally the intl-tel-input jQuery plugin.

We then looked at some of the issues around validation, and why common approaches such as regular expressions are often inadequate, particularly when you go international.

We took a look at Google’s libphonenumber library; using it to parse, validate, display and determine the type of telephone numbers.

We combined the intl-tel-input plugin with libphonenumber for an even better user experience, albeit one which comes at a cost in terms of performance.

Finally we looked at how we might mark up telephone numbers in our HTML.

There are a few recommendations I would make for dealing with telephone numbers:

  • Unless you only operate in a single country, be aware of the international differences.
  • Use masked inputs with caution.
  • Be very careful with regular expression-based validation.
  • Where possible, use E.164 for storage.
  • Use Google’s libphonenumber library.
  • When displaying numbers, format them where possible, use the tel: or callto: link type, and use Microdata.
  • Rookie

    This is great! Learnt a lot and bookmarked this article :)

  • lingceng

    type error: We can also use Microdata to mark up “telephone” numbers semantically.

    • LouisLazaris

      Thanks, it’s fixed. :)

  • Wilson

    learnt a lot from this, thanks

  • Eliran Yonani

    can you please add a possiblity to put to forms in one page?
    right now its working only if the code running one time in a page..
    thanks ahead

  • http://www.snapyshop.com/ Sarath Uch

    I saw a code to remove 0 zero, by using typecasting parseInt();

    There’s a small logic there, but I have checked yet. parseInt(‘012345678’, 10); // it return 12345678

  • ViceSpy

    Very in-depth article. Rarely do you see such passion for the micro tasks of development but this is the kind of stuff that makes applications pleasing to look at and work with! Thank you! Also major props to Bluefieldscom for the great script.

  • http://codelocalization.com Paul Tomkiel

    Well, this is awkward, I found this article in the middle of writing my own on the same topic. Fortunately enough, we focused on different aspects ;) Keep up the good work!

  • Eyal

    Any idea how services like Byteplant operate? What do they use to determine if a number is in service?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in JavaScript, once a week, for free.