Don’t wait, let’s use the browser Contact Picker API now

Cover for Don’t wait, let’s use the browser Contact Picker API now

Topics

The gap between native mobile applications and browsers continues to close: case in point, the browser’s Contact Picker API. Just as with mobile apps, it’s useful for chats, ordering food or taxis, perhaps splitting the bill with a few people, and more. We’ll explain what the Contact Picker API is, why we need it, how to use it right now, and some potential pitfalls to avoid.

🚨 Throughout this post, we’ll reference a demo we’ve created to showcase the Contact Picker API, which you can find here: https://contact-picker-api-demo.vercel.app.

Feel free to launch it and check it out (ideally from your mobile device.) The demo, which is made to resemble an app for splitting a bill with multiple people, showcases essentially all the functionality of the API, and, in addition, you’re welcome to dig into the repo on GitHub to check the code for youself.

A complete view of the browser Contact Picker API in action. The total number in the Money field has been evenly divded and displayed along with the chosen contacts' info, their names, emails, and phone numbers.

Here’s the demo in action: after pressing the Pick button, the contacts will be selected, the total number in the Money field will be evenly divded and displayed along with the chosen contacts’ info.

More contact context

Let’s leap back a little bit for some context. The publication of the public working draft hasn’t just come out of the blue—in fact, the Chrome team noted a need for a solution like this years ago, and they began working on an API making user contacts available with the security and privacy expected on the web.

Then, in late 2022, the Devices and Sensors Working Group and Web Applications Working Group published the first public working draft of the Contact Picker API.

That’s interesting news. (But hardly unexpected news, as mentioned, even the Chrome team recognized a need for this years ago.) And that brings us to here: the Contact Picker API defines an API to give one-off access to a user’s contact information while keeping the user in control over which contacts are shared.

Why do we even need this?

Within browser apps, the ability to chat, take and upload photos, is pretty much a given now.

But what about the ability to access a user’s contact information? Why hasn’t this feature appeared? Isn’t the gap between web apps and native apps supposed to be closing?

Access to contacts would provide handy ways of inviting friends to chats (or discovering those who are already there), easily send emails to contacts, select a number to make a VoIP call—and that’s just the tip of the iceberg!

The bottom line is this: sooner or later, the Contact Picker API feature is going to see widespread adoption, so we need to be ready now.

Browser support for the Contact Picker API as of April 2023. Expect this to have a lot more green in the near future.

Browser support for the Contact Picker API as of April 2023. Expect this to have a lot more green in the near future.

The recent W3C draft publication is the proof in the pudding, but this has been a long time coming. So, if we need to be ready—let’s get ready.

Some necessary preparations to work with the Contact Picker API

A quick note: before development and testing with the Contact Picker API, you’ll need to enable support for the feature on your device. On Android, it’s now available in Chrome 80 on Android M or later by default. However, if you’re working with iOS, the Contact Picker API is considered an experimental feature and you’ll need to follow these instructions:

  1. Go to Settings, scroll down, and select Safari.
  2. Scroll down and tap the Advanced option and toggle the Web Inspector option.
  3. Select the Experimental Features menu and toggle the Contact Picker API.

Now, you can connect your Mac to debug and explore the Contact Picker API. There’s a great guide here if you need further assistance.

Disassembling the Contact Picker API

Let’s get down to business and start describing the API itself. The ContactsManager interface is found on the contacts property on the navigator object.

We can write some code to make sure everything is as it should be:

export const isContactsSupported =
  "contacts" in navigator && "ContactsManager" in window;

Why do we need this upfront when using this API?

So, for example, in the case of the restaurant bill splitting app in our demo, users could have devices that don’t support the Contact Picker API yet, and in these cases, you don’t want to activate app features related to the Contact Picker API for those users.

Now, we’re going to focus on the two important methods this API gives us.

Properly getting into getProperties

Let’s begin with the getProperties method. This method returns the contact properties that are available on the current user device; there is a defined list of properties that can be returned: names, telephone numbers, emails, addresses, and icons:

interface ContactsManager {
  getProperties: () => Promise<ContactProperty[]>;
}

enum ContactProperty {
  "address" = "address",
  "email" = "email",
  "icon" = "icon",
  "name" = "name",
  "tel" = "tel",
}

This method comes in handy if we just want to deal with specific categories of contact information. And of course, it’s also useful because it will allow us to write code depending on the properties that are available on a particular user’s device. We can adapt as needed, or gracefully display a message about unavailablity, as seen in the demo project’s file SettingsForm.tsx:

async function checkPropertiesSupport(): Promise<void> {
  try {
    const supportedProperties = await navigator.contacts.getProperties();
    setContactProperties(supportedProperties);
  } catch {
    console.warn(
      "This browser doesn’t support the Contact Picker API, which is required for this demo."
    );
  }
}

The list of supported properties will be returned as an array, for example, in the case above, we might get an array like ["email", "name", "tel"] and then assign it to supportedProperties.

By the way, it’d also be easy to check for specific properties by using the includes method:

const supportedProperties = await navigator.contacts.getProperties();
  if (supportedProperties.includes("email")) {
    console.log("You've got mail!");
  }

A select few bits of info on using select

The select method will show users a modal UI to select contacts, and it will then return a Promise. If the promise resolves, it will return an array (even if only one contact was selected) filled with ContactInfo interfaces. For reference, here’s what a ContactInfo interface looks like:

interface ContactsManager {
  select: (
    properties: ContactProperty[],
    options?: ContactsSelectOptions
  ) => Promise<ContactInfo[]>;
}

interface ContactInfo {
    address: Array<ContactAddress>;
    email: Array<string>;
    icon: Blob;
    name: Array<string>;
    tel: Array<string>;
};

In practical terms, the meaning of most of the values above are plainly obvious, like name, tel,email, and address. But, just in case you’re wondering what the icon value is talking about, this refers to the avatar image of a contact.

Back to the select method, it takes two arguments:

  • An array of contact properties you want
  • An optional second argument, the multiple property, which can be true or false to specify if you want the ability to get one vs. multiple contacts.

Enough talk, here’s what the basic syntax looks like:

async function selectContacts () {
 const contacts = await navigator.contacts.select(['name', 'tel'], { multiple: true });

 if (!contacts.length) {
   // no contacts were selected.
   return;
 }

  return contacts;
}

selectContacts();

/*
[
  {"name":["Jim Teller"],"tel":["+61412345678","+447123456789"]},
  {"name":["Susan Field"],"tel":["+61412345678","+447123456789"]}
]
*/

Of course, our demo project allows you to customize a few things. For instance, you can choose to allow for the selection of single contact or multiple, and you can also toggle the properties you wish to achieve (although such a toggle is for demo purposes, as this would be unlikely to be something you’d give users control over in a real project):

A section of the Contact Picker API demo that shows toggles for different settings, like single or multiple contact selection, or if email, name, phone, or icon will be displayed for the contacts.

The corresponding code in the demo app within the ContactsList.tsx file looks like this:

const handlePickClick = useCallback(async () => {
  const contacts = await navigator.contacts.select(selectedProperties, {
    multiple: true
  });

  contactsStore.set(contacts);
}, [settings]);

What about security concerns?

Let’s have a quick note on this. In general, security and privacy were a key consideration when formulating the working draft, and it probably outlines this all the best:

  • The API is only available top-level browsing context.
  • That context must also be secure (HTTPS).
  • The API cannot be initiated programmatically; that is, it requires a user gesture, the same contact UI that would appear within a native application.

Moving on

The Contact Picker API is not a particular difficult concept to grasp, nor is this solution difficult to implement. And, further, since this feature has existed conceptually within native applications for a long time, we don’t need to reinvent the wheel in terms of features (although the door to creativity is certainly still open). The only thing preventing it from achieving widespread compatibility is probably a lack of adoption. My hope is that it’s clear that we’ve already got all the tools we need to begin doing that now.

So, as we sign off from Mars, let’s get out there and make first contact! 🛸

Speaking of first contact, you don’t have to wait for SETI’s satellite’s to get a ping from the great cosmic abyss: Evil Martians’ services are available for earthlings everywhere! We’re ready to beam down and fix your troubles, whether they be related to frontend, product design, backend, devops or beyond, get in contact now for more info!

Join our email newsletter

Get all the new posts delivered directly to your inbox. Unsubscribe anytime.