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.
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.
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.
It’s often important to capture a number’s international dialling 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 dialling 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 efficient 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 dialling 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.
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:
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.
First though, let’s look at the issue of collecting telephone numbers.
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:
Alternatively, you can break a number down into separate elements:
() -
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:
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.
There is a better and more flexible way to collect telephone numbers, in the form of an excellent jQuery plugin. It’s illustrated below.
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:
Finally, intialize it as follows:
$("#number").intlTelInput();
For a full list of configuration options, consult the documentation. Later, we’ll look at the option, but first, we need to delve into another useful library.
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.
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.
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 dialling code:
var tel = phoneUtil.parse('2024561111');
This will throw the following exception:
Error: Invalid country calling code
This is because without explicitly 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.
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 implicitly 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"
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 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:
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.
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.
Legal
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:
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.
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:
+1 202-456-1111
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.
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:
Acme Corp, Inc.
Phone: 202-456-1111
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:
Thank you Praveen Kumar for the blog!
We are a leading niche digital & tech recruitment specialist for the North East of England. We Specialise in the acquisition of high-performing technology talent across a variety of IT sectors including Digital & Technology Software Development.
Our ultimate goal is to make a positive impact on every client and candidate we serve - from the initial call and introduction, right up to the final delivery, we want our clients and candidates to feel they have had a beneficial and productive experience.
If you’re looking to start your journey in sourcing talent or find your dream job, you’ll need a passionate, motivated team of experts to guide you. Check out our Jobs page for open vacancies. If interested, contact us or call 0191 620 0123 for a quick chat with our team.
Follow us on our blog, Facebook, LinkedIn, Twitter or Instagram to follow industry news, events, success stories and new blogs releases.
Back to Blog