Skip to main content
Photography of Alvaro Montoro being a doofus
Alvaro Montoro

Avid Reader

A phone standing

Read Phone Contacts with JavaScript

Not so long ago, accessing data from the phone using JavaScript was unimaginable. Now, using the Contact Picker API, things like reading the contact list are possible-and really simple.

javascript html webdev



Reading entries from the contact list on a phone or tablet has traditionally been limited to native apps. But with the Contact Picker API, we can do just that using JavaScript.

This feature can be interesting in apps that need contact information like phone numbers or VoIP, social networks where we want to discover known people, or apps that require filling in form information without swapping applications to view the data.

The API and the device will limit which properties will be available. There are five standard ones that developers can select:

  • Names
  • Phones
  • Emails
  • Addresses
  • Icons

The plurals here are important, as a contact can have more than one phone, email, or multiple addresses. The returned data will always be inside arrays for consistency, even if it is a single value. More on that later.

Privacy and Security

The contact information stored on a phone can contain sensitive information that we must treat carefully. Because of that, there are privacy and security considerations that we must take into account:

  • The Contact Picker API code must run in the top-level browsing context. It prevents external code, like ads or third-party plugins, from reading the contacts list on your phone.
  • The Contact Picker API code can only run after a user gesture. Thus, developers cannot fully automate the process. The user must act to trigger the contact reading.
  • The person must allow access to the contact list. This restriction is imposed by the phone and not by JS. The user must grant the browser permission to access the contacts (if it doesn't already have it).

The first time that they use a website that uses the Contact Picker API, they may get a message like this:

Screenshot of android pop-up requesting permission to access the contact list
Some people may find this pop-up "scary."

The phone will display this pop-up every time until the user taps "Allow." The Contact Picker API won't run until that happens. Which is good; we want to ensure users grant the proper permissions. It's also good that it's a one-time thing; granting authorization each time the page runs the Contact Picker API code would be a pain in the neck.

The API and Code

The Contact Picker API only defines two methods:

  • getProperties(): returns a list of the properties available to read on the device. In the definition, there are only five: "address", "email", "icon" (this may not be the contact picture), "name", "tel" (telephone), but the device may not allow access to all of them.
  • select(): opens the contact pop-up and returns the selection once the user completes the action. It takes two parameters: a list of the properties to read and an optional object with options.

Both methods return promises, but taking into account that the actions that they trigger block the regular flow of the app, we should use async / await when handling them.

It may be tempting to ignore getProperties() and request all the properties directly. But beware if you do that: it will likely work, but if any of the specified properties is unavailable, the select() method will throw an exception.

Example

A demo of the Contact Picker API is in action (run it online here or watch this video). If the API is supported, it shows a button that reads the contact's telephone number, name, and email to display it.

First, we need the button. As detailed earlier in the Privacy and Security section, a user action is required before we can call the API, so we cannot trigger anything without user interaction:

<button onclick="getContactData()">Show contact data</button>

The main code will be in the getContactData() function. But before that, what would be the point of showing the button if the Contact Picker API is unavailable? Let's hide the button if it is not available. Or even better, let's hide the button by default (adding the hidden attribute) and only show it if the API is available.

// only show the button if browser supports Contact Picker API
if ("contacts" in navigator) {
  document.querySelector("button").removeAttribute("hidden");
}

Now that the button logic is in place let's focus on getContactData(). Here's a commented version of the function:

// it is asynchronous because we'll wait for the modal selection
async function getContactData() {
  // indicate what contact values will be read
  const props = ["tel", "name", "email"];

  // wrap everything in a try...catch, just in case
  try {
    // open the native contact selector (after permission is granted)
    const contacts = await navigator.contacts.select(props);

    // this will execute after the native contact selector is closed
    if (contacts.length) {
      // if there's data, show it
      alert("Selected data: " + JSON.stringify(contacts));
    } else {
      // ...if not, indicate nothing was selected
      alert("No selection done");
    }
  } catch (ex) {
    // if something fails, show the error message
    alert(ex.message)
  }
}

Once the button triggers this function, and if the browser has permissions (see screenshot in the previous section), the contact modal will show up, indicating essential information: the URL reading the data, what data it will return, and the list of contacts to pick from.

Screenshot of the android native contact picker modal
The Contact Picker shows what information will be shared

After closing the modal, the contacts variable will store the data in JSON as an array with an object containing the information requested (it may be empty if it is not available in the contact card).

For example, this is the result after selecting myself as a contact (fake data):

[
  {
    "address": [],
    "email": [ "alvarosemail@gmail.com" ],
    "icon": [],
    "name": [ "Alvaro Montoro" ],
    "tel": [ "555-555-5555", "555-123-4567" ]
  }
]

If the data includes an icon, it will be a blob with the image. If the data includes an address, it will be a more complex object with street, city, country, ZIP code, etc. You can check the returned values in the specification.

But why an array if we only selected one contact? Because there's an option to choose more than one contact!

Selecting Multiple Contacts

It is possible to select more than one contact. If we want to do that, we need to pass a second parameter to the navigator.contacts.select() method indicating this option.

const props = ["tel", "address", "icon", "name", "email"];
// only one option available: read multiple or only one (default)
const options = { multiple: true };

try {
  const contacts = await navigator.contacts.select(props, options);
  // ...

The result is an array of contacts, so the rest of the code could remain the same for this example.


The code above can be intimidating, mainly because of all the comments I added. Here's a lightly commented version of the code above. As you may notice, it is pretty simple:

async function getContactData() {
  if ("contacts" in navigator) {
    const props = await navigator.contacts.getProperties();
    const options = { multiple: true };

    try {
      const contacts = await navigator.contacts.select(props, options);

      if (contacts.length) {
        // code managing the selected data
      } else {
        // code when nothing was selected
      }
    } catch (ex) {
      // code if there was an error
    }
  }
}

You can check out a running demo on my website. Don't worry, I don't do any with the contact information beyond writing it on the screen. But review the code before if you don't trust me.

Conclusion: Privacy Over Piracy

Contact information is PII (Personally Identifiable Information), and we must treat it with all the care and security that sensitive data requires.

Apart from possible legal requirements that I am not going to go through (because I don't know them, and they change from country to country), here are some basic guidelines when dealing with sensitive data:

  • Respect people's privacy. Don't force them to share information they don't want to share.
  • Treat the data with care and in a safe way. Would you be comfortable if the data you are processing were yours?
  • Don't store data if you don't need to. Read it, use it, forget it. Don't store data that you are not using.
  • Only get the data that you need. Don't be sneaky or shady. Get just what's required to build credibility and trust.

Suppose a web app tries to read addresses, names, or emails while selecting a phone number. If that happened to me, I would automatically reject the permission and leave the website.

So, explore JavaScript and the Contact Picker API, but always remember that there's a person behind the screen and that the data they share could be risky if it falls into the wrong hands. Don't be reckless.



If you enjoyed this article about JavaScript and like to test Web APIs and different things with JS, check out this other article:

Develop a Rock Band game with HTML and JavaScript
Drumset gamepad illustration

Article originally published on