Tutorial
Using the Contact Picker API in an Android TWA
September 9, 2026 · 7 min read
When packaging a Progressive Web App (PWA) into an Android app store package using a Trusted Web Activity (TWA), developers often assume they must write custom Kotlin code or rely on heavy hybrid frameworks to access native system features. This is a common misconception. Because TWAs run on top of the system browser engine, standard modern web APIs are executed with native performance. One of the most powerful APIs available is the Contact Picker API, which allows web apps to request secure access to the device contact list.
Using the Contact Picker API in an Android TWA provides a completely native look and feel. The platform handles the user interface, permissions, and security parameters directly. This tutorial explains how to implement the Contact Picker API, manage property permissions, parse the returned data, and establish fallback interfaces for other operating environments.
How the Contact Picker API Works in TWAs
The Contact Picker API functions under a strict security model. When your TWA invokes the contact picker, the browser engine temporarily pauses your web application context and opens the native Android contact selection interface. The user retains complete control over which contacts they choose to share. This design model ensures your web application cannot scrape the entire contact book silently, which is an important consideration for maintaining compliance with Google Play Store data safety and privacy policies.
For the API to function within a TWA, the underlying browser (typically Google Chrome) must support the feature, and the TWA must have verified its ownership of the domain via Digital Asset Links. Once verified, the transitions between the web layer and the system dialogs occur seamlessly without security warning popups.
Implementing the Contact Picker API
To implement contact picking, you must first verify API compatibility in the current browser environment. This is achieved by checking if the contacts property exists on the navigator object. If supported, you can query which specific properties the system allows you to retrieve, such as names, telephone numbers, emails, addresses, or profile icons.
Below is a technical implementation demonstrating how to check for API support, configure selection criteria, and handle the returned results using asynchronous JavaScript.
async function selectContacts() {
if ('contacts' in navigator && 'ContactsManager' in window) {
const supportedProperties = await navigator.contacts.getProperties();
const options = { multiple: true };
try {
const contacts = await navigator.contacts.select(supportedProperties, options);
handleSelectedContacts(contacts);
} catch (err) {
console.error('Contact selection cancelled or failed: ', err);
}
} else {
fallbackContactMethod();
}
}
The function starts by querying the available properties using the getProperties method. This is an essential step because hardware manufacturers or security suites may limit certain fields. Next, the select method is invoked, accepting the supported properties array and an optional configuration object. Setting the multiple property to true allows the user to check multiple contacts from their list simultaneously.
Contact Properties and Schema Structures
The data returned by the select promise is structured as an array of contact objects. Each contact object corresponds to a specific database entry on the Android operating system. It is vital to understand the schema structure to parse the values without throwing runtime exceptions.
| Property Name | Returned Data Type | Description |
|---|---|---|
| name | Array of Strings | The full names of the contact, as formatted in the address book. |
| tel | Array of Strings | All telephone numbers associated with the selected contact. |
| Array of Strings | All email addresses registered for the contact. | |
| address | Array of Objects | Physical addresses including street, city, region, country, and postal code. |
| icon | Array of Blobs | Image files of the contact avatar, available as binary large objects. |
To safely consume this data, your JavaScript controller must iterate through these arrays, as users often have multiple numbers or email addresses saved under a single contact profile. The following pattern demonstrates parsing phone numbers and names from the results:
function handleSelectedContacts(contacts) {
contacts.forEach(contact => {
const primaryName = contact.name && contact.name[0] ? contact.name[0] : 'Unknown';
const phoneNumbers = contact.tel ? contact.tel.join(', ') : 'No Phone Number';
console.log('Contact Name: ' + primaryName + ' | Phones: ' + phoneNumbers);
});
}
Managing Blobs for Contact Icons
If your application requires displaying the contact avatar, you will receive the image as a Blob. To render this image inside your HTML document, you must convert the Blob into a temporary object URL. This URL can then be assigned directly to the source attribute of an image element.
if (contact.icon && contact.icon[0]) {
const blob = contact.icon[0];
const imageUrl = URL.createObjectURL(blob);
const imgElement = document.createElement('img');
imgElement.src = imageUrl;
document.body.appendChild(imgElement);
}
To prevent memory leaks within your TWA, remember to revoke the object URL using URL.revokeObjectURL once the image has successfully loaded or when the component is unmounted from the DOM.
Designing Fail-Safe Fallback Experiences
Because your PWA can be run outside of the TWA environment (such as on desktop browsers, older Android operating systems, or iOS devices), you must maintain a functional fallback experience. If the feature check fails, you should present an alternative input interface.
A common design pattern involves replacing the direct native selection button with a standard text input field, allowing users to type a contact number or copy-paste the values manually. This ensures that the application interface remains usable across all targets, while providing an optimized, premium native experience specifically for verified TWA installations.
Data Privacy and Store Compliance
Google Play Store policies require developers to explain why they are collecting personal contact information. When you distribute your TWA on Google Play, your store listing must include a detailed privacy policy, and you may be prompted to fill out the Data Safety form in the Google Play Console.
By using the standard Contact Picker API instead of a custom native bridge, you demonstrate to Google and your users that your application does not have ambient, background access to the phone book. The app only gains access to data specifically chosen by the user through the sandboxed system dialog. Explain this distinction clearly in your application privacy documentation to streamline the app store approval process.
Ready to ship your Android app?
Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.
Build my app