Author's note: The technologies and processes described in this article are experimental and will only work in a few browsers. At the moment of writing, the Contact Picker API was only supported by Android Chrome (from version 80) and iOS Safari (from version 14.5, but only behind a flag). If you want to review the functionality, you can check a running demo on my website.
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:
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.
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:
Top comments (4)
Thank you for the article! I was unaware of this API and hope it becomes available in all browsers, especially iOS Safari.
Thanks! You can test it on iOS Safari by activating the API here: Settings > Safari > Advanced > Experimental Features, and enable the Contact Picker API.
It's supposed to work on Safari on Mac, too, but I always get an "Out of memory" error :(
this would be helpful to create an automation whenever i add a new contact, it sends them my visiting card.
will give it a try!