Contact Picker API in Chrome Extensions: Complete Guide to Accessing User Contacts

23 min read

Contact Picker API in Chrome Extensions: Complete Guide to Accessing User Contacts

Contact Picker API in Chrome Extensions: Complete Guide to Accessing User Contacts

The Contact Picker API represents one of the most user-centric additions to Chrome’s extension platform, enabling developers to create extensions that can access and utilize users’ contact information directly from their Google account or device address book. This powerful API opens up tremendous possibilities for building communication-focused extensions, email clients, CRM tools, and social applications that need to interact with users’ contact collections. Whether you’re developing a contact management extension, a mailing list builder, or a communication hub that needs to access addresses from the user’s address book in Chrome, understanding how to properly implement the Contact Picker API is essential for creating extensions that feel native to the Chrome ecosystem.

This comprehensive guide will walk you through everything you need to know about implementing contact picker functionality in your Chrome extensions. We’ll cover the fundamental concepts behind the Contact Picker API, the required permissions and manifest configurations, step-by-step implementation patterns, and real-world use cases that demonstrate how to build powerful contact management features. By the end of this guide, you’ll have the knowledge and practical code examples needed to create professional-grade extensions that can seamlessly access and utilize user contacts while maintaining privacy and security.


Understanding the Contact Picker API

The Contact Picker API is a browser-native interface that allows web applications and extensions to access the user’s contact information through a standardized, privacy-preserving mechanism. Unlike older approaches that required full access to contacts databases, the Contact Picker API operates on a user-controlled selection model where users explicitly choose which contacts to share with the requesting application. This design philosophy ensures that users maintain complete control over their personal information while still enabling developers to create rich, contact-aware experiences.

The API was initially introduced for progressive web applications (PWAs) and has since been extended to work within Chrome extensions using the same underlying mechanisms. When a user invokes the contact picker in your extension, Chrome presents a native contact selection interface that displays all available contacts from the user’s Google account and synced device contacts. The user can then select one or more contacts and choose exactly which fields (such as name, email, phone number, or address) to share with your extension. This granular control over data sharing is a significant advancement over traditional contact access methods that typically required broad permissions.

Key Features and Capabilities

The Contact Picker API provides several powerful features that make it an attractive choice for extension developers. First and foremost is the built-in privacy mechanism that ensures users must actively select contacts and approve field sharing for each interaction. This stands in contrast to older APIs that might grant persistent access to entire contact databases. The API also supports multiple contact selection, allowing users to choose several contacts at once when your extension needs to work with groups or lists.

Another significant advantage is the native user interface that Chrome provides. Rather than building custom contact selection UI components (which can be complex and inconsistent across different implementations), your extension can leverage Chrome’s built-in contact picker that follows Material Design guidelines and provides a familiar experience for Chrome users. The API also handles contact deduplication and synchronization automatically, presenting users with a unified view of their contacts regardless of whether they’re stored in Google Contacts, device memory, or other synced sources.


Required Permissions and Manifest Configuration

To implement the Contact Picker API in your Chrome extension, you need to configure your manifest.json file with the appropriate permissions and declare the API requirements. Unlike some other Chrome APIs that offer optional permission models, the Contact Picker API requires specific declarations to function properly.

Declaring Permissions in Manifest V3

For Manifest V3 extensions (which is the current standard and recommended version), you need to include the "contacts" permission in your manifest. This permission enables your extension to request access to the contact picker functionality. Here’s a complete example of a manifest.json configuration that includes the contacts permission:

{
  "name": "Contact Manager Pro",
  "version": "1.0",
  "manifest_version": 3,
  "description": "A powerful contact management extension with address book integration",
  "permissions": [
    "contacts"
  ],
  "host_permissions": [
    "https://contacts.google.com/*"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  }
}

The "contacts" permission is the core requirement that enables the Contact Picker API to function within your extension. Additionally, the host permission for contacts.google.com is required to allow the API to communicate with Google’s contact services where user contacts are stored and synchronized. It’s important to note that requesting this permission will cause Chrome to display a warning during installation, informing users that the extension can access contact information. Being transparent about why your extension needs contact access and how it uses that data is crucial for building user trust.

Understanding Permission Scopes

The Contact Picker API operates on a request-response model rather than granting persistent access to the entire contact database. When your extension calls the API, Chrome prompts the user to select contacts and choose which fields to share. This means your extension doesn’t have continuous access to all contacts but rather receives contact data only after explicit user approval for each interaction. This design significantly reduces the privacy risks associated with contact access and aligns with modern privacy best practices.

The permission declaration serves as a signal to users that your extension has the capability to access contacts, while the actual data sharing is controlled at runtime through the user selection interface. This two-layer approach (permission declaration plus runtime consent) provides a good balance between functionality and privacy protection.


Implementing the Contact Picker API

Now let’s explore the practical implementation of the Contact Picker API in your Chrome extension. The API is exposed through the navigator.contacts object in extension contexts, providing methods to query available contacts and retrieve selected contact information.

Basic Contact Picker Implementation

The fundamental method for accessing contacts is navigator.contacts.select(), which opens the Chrome contact picker interface and returns the selected contacts to your extension. Here’s a comprehensive example demonstrating how to implement basic contact selection:

// In your extension's JavaScript file (e.g., popup.js or content script)

document.addEventListener('DOMContentLoaded', () => {
  const selectContactsBtn = document.getElementById('select-contacts');
  
  selectContactsBtn.addEventListener('click', async () => {
    try {
      // Check if Contact Picker API is available
      if (!navigator.contacts || !navigator.contacts.select) {
        console.error('Contact Picker API not supported');
        return;
      }
      
      // Define which contact properties you want to access
      const properties = ['name', 'email', 'tel', 'address'];
      
      // Open the contact picker and get selected contacts
      const contacts = await navigator.contacts.select(properties, {
        multiple: true  // Allow selecting multiple contacts
      });
      
      // Process the selected contacts
      handleSelectedContacts(contacts);
      
    } catch (error) {
      console.error('Error selecting contacts:', error);
    }
  });
});

function handleSelectedContacts(contacts) {
  console.log('Selected contacts:', contacts);
  
  // Example: Display contact names
  contacts.forEach(contact => {
    const name = contact.name ? contact.name[0] : 'Unknown';
    const email = contact.email ? contact.email[0] : 'No email';
    const phone = contact.tel ? contact.tel[0] : 'No phone';
    
    console.log(`Name: ${name}, Email: ${email}, Phone: ${phone}`);
  });
}

This implementation demonstrates several key aspects of the Contact Picker API. First, we check for API availability to ensure graceful degradation on browsers or contexts where the API isn’t supported. Second, we specify an array of properties we want to access (name, email, telephone, and address), which Chrome will present to users for their approval. Third, we enable multiple selection to allow users to pick more than one contact at a time. Finally, we process the returned contact objects, which contain arrays of values for each requested property.

Handling Contact Properties

Understanding the structure of contact data returned by the API is essential for building robust extensions. The Contact Picker API returns contact objects where each property is an array, allowing for multiple values per property type. This reflects the reality that people often have multiple email addresses, phone numbers, or physical addresses. Here’s a more detailed look at working with contact properties:

// Comprehensive contact property handling

function processContactDetails(contact) {
  const details = {
    names: contact.name || [],
    emailAddresses: contact.email || [],
    phoneNumbers: contact.tel || [],
    addresses: contact.address || []
  };
  
  // Extract primary values (first in each array)
  const primaryName = details.names[0] || 'Unknown';
  const primaryEmail = details.emailAddresses[0] || null;
  const primaryPhone = details.phoneNumbers[0] || null;
  
  // Display all available values
  console.log(`Primary Contact: ${primaryName}`);
  
  if (details.emailAddresses.length > 1) {
    console.log('All email addresses:');
    details.emailAddresses.forEach((email, index) => {
      console.log(`  ${index + 1}. ${email}`);
    });
  }
  
  if (details.phoneNumbers.length > 1) {
    console.log('All phone numbers:');
    details.phoneNumbers.forEach((phone, index) => {
      console.log(`  ${index + 1}. ${phone}`);
    });
  }
  
  return { primaryName, primaryEmail, primaryPhone };
}

This pattern of handling contact properties ensures your extension can work with the full richness of contact data while gracefully handling cases where certain properties might be missing or where users have multiple values for a single property type.


Building a Contact Picker Extension

Let’s put everything together and build a complete, functional contact picker extension that demonstrates best practices for using the Contact Picker API. This example will create a popup-based extension that allows users to select contacts and view their details.

Project Structure

A well-organized project structure is essential for maintainable Chrome extensions. For our contact picker extension, we’ll use the following structure:

contact-picker-extension/
├── manifest.json
├── popup.html
├── popup.js
├── styles.css
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

Complete Implementation

Here’s the complete implementation of our contact picker extension, starting with the manifest:

// manifest.json
{
  "name": "Easy Contact Picker",
  "version": "1.0",
  "manifest_version": 3,
  "description": "Quickly select and export contacts from your address book in Chrome",
  "permissions": [
    "contacts"
  ],
  "host_permissions": [
    "https://contacts.google.com/*"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}
<!-- popup.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Picker</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <h1>Contact Picker</h1>
    <p class="subtitle">Select contacts from your address book in Chrome</p>
    
    <button id="select-contacts" class="primary-button">
      Choose Contacts
    </button>
    
    <div id="results" class="results-container">
      <h2>Selected Contacts</h2>
      <ul id="contact-list"></ul>
    </div>
    
    <div id="export-section" class="export-section" style="display: none;">
      <button id="export-csv" class="secondary-button">Export as CSV</button>
      <button id="export-json" class="secondary-button">Export as JSON</button>
    </div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>
/* styles.css */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 350px;
  padding: 20px;
  background-color: #fafafa;
}

.container {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

h1 {
  font-size: 20px;
  color: #333;
  margin-bottom: 4px;
}

.subtitle {
  font-size: 13px;
  color: #666;
  margin-bottom: 8px;
}

.primary-button {
  background-color: #4285f4;
  color: white;
  border: none;
  padding: 12px 20px;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}

.primary-button:hover {
  background-color: #3367d6;
}

.secondary-button {
  background-color: #f5f5f5;
  color: #333;
  border: 1px solid #ddd;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.secondary-button:hover {
  background-color: #e8e8e8;
}

.results-container {
  margin-top: 12px;
}

h2 {
  font-size: 14px;
  color: #333;
  margin-bottom: 8px;
}

#contact-list {
  list-style: none;
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
}

.contact-item {
  padding: 10px 12px;
  border-bottom: 1px solid #f0f0f0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.contact-item:last-child {
  border-bottom: none;
}

.contact-name {
  font-weight: 500;
  color: #333;
}

.contact-detail {
  font-size: 12px;
  color: #666;
}

.export-section {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}
// popup.js
let selectedContacts = [];

document.addEventListener('DOMContentLoaded', () => {
  const selectButton = document.getElementById('select-contacts');
  const exportCsvButton = document.getElementById('export-csv');
  const exportJsonButton = document.getElementById('export-json');
  
  selectButton.addEventListener('click', handleContactSelection);
  exportCsvButton.addEventListener('click', exportAsCsv);
  exportJsonButton.addEventListener('click', exportAsJson);
});

async function handleContactSelection() {
  if (!navigator.contacts || !navigator.contacts.select) {
    alert('Contact Picker API is not supported in this browser.');
    return;
  }
  
  try {
    const properties = ['name', 'email', 'tel', 'address'];
    
    const contacts = await navigator.contacts.select(properties, {
      multiple: true
    });
    
    selectedContacts = contacts;
    displayContacts(contacts);
    
  } catch (error) {
    console.error('Error accessing contacts:', error);
    alert('Failed to access contacts. Please ensure you have granted permission.');
  }
}

function displayContacts(contacts) {
  const contactList = document.getElementById('contact-list');
  const resultsContainer = document.getElementById('results');
  const exportSection = document.getElementById('export-section');
  
  contactList.innerHTML = '';
  
  if (contacts.length === 0) {
    contactList.innerHTML = '<li class="contact-item">No contacts selected</li>';
    exportSection.style.display = 'none';
    return;
  }
  
  contacts.forEach(contact => {
    const li = document.createElement('li');
    li.className = 'contact-item';
    
    const name = contact.name ? contact.name[0] : 'Unknown';
    const email = contact.email ? contact.email[0] : '';
    const phone = contact.tel ? contact.tel[0] : '';
    
    li.innerHTML = `
      <span class="contact-name">${escapeHtml(name)}</span>
      ${email ? `<span class="contact-detail">${escapeHtml(email)}</span>` : ''}
      ${phone ? `<span class="contact-detail">${escapeHtml(phone)}</span>` : ''}
    `;
    
    contactList.appendChild(li);
  });
  
  resultsContainer.style.display = 'block';
  exportSection.style.display = 'flex';
}

function exportAsCsv() {
  if (selectedContacts.length === 0) return;
  
  let csv = 'Name,Email,Phone\n';
  
  selectedContacts.forEach(contact => {
    const name = contact.name ? contact.name[0] : '';
    const email = contact.email ? contact.email[0] : '';
    const phone = contact.tel ? contact.tel[0] : '';
    
    csv += `"${name}","${email}","${phone}"\n`;
  });
  
  downloadFile(csv, 'contacts.csv', 'text/csv');
}

function exportAsJson() {
  if (selectedContacts.length === 0) return;
  
  const json = JSON.stringify(selectedContacts, null, 2);
  downloadFile(json, 'contacts.json', 'application/json');
}

function downloadFile(content, filename, mimeType) {
  const blob = new Blob([content], { type: mimeType });
  const url = URL.createObjectURL(blob);
  
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

This complete implementation demonstrates a production-ready contact picker extension that includes proper error handling, user interface design, and export functionality. Users can select contacts from their address book in Chrome, view their details, and export the selected contacts in both CSV and JSON formats.


Best Practices and Security Considerations

When implementing Contact Picker API functionality in your Chrome extensions, following best practices ensures both user satisfaction and security compliance. The contact information that users share through your extension represents sensitive personal data that requires careful handling.

Privacy and Security Best Practices

First and foremost, always request only the contact properties your extension actually needs. Requesting unnecessary properties like addresses or phone numbers when you only need names creates unnecessary privacy exposure and may make users hesitant to grant access. Be specific about your requirements and only ask for the minimum data necessary to accomplish your extension’s purpose.

Another critical practice is to clearly communicate to users why you need contact access and how their data will be used. Include this information in your extension’s description on the Chrome Web Store and in your extension’s in-app messaging. Users are more likely to grant contact access when they understand the benefit to them and trust that their data will be handled responsibly.

When storing contact data within your extension (using chrome.storage or other persistence mechanisms), ensure that you implement appropriate security measures. Contact information should be encrypted at rest, and you should provide users with the ability to delete stored contacts. Consider implementing data retention policies that automatically remove old contact data unless users explicitly choose to keep it.

Error Handling and User Experience

Robust error handling is essential for contact picker implementations. Users may encounter various error conditions: they might not have any contacts saved, they might revoke permission, or the Contact Picker API might not be available in their browser context. Your extension should handle each of these scenarios gracefully with informative error messages that help users understand what happened and what they can do about it.

Always provide feedback to users during the contact selection process. Since the contact picker opens in a separate Chrome interface, users might not realize that their extension is waiting for them to make a selection. Consider adding loading states or instructional text that indicates your extension is awaiting contact selection.


Advanced Use Cases

The Contact Picker API enables various advanced use cases beyond simple contact selection. Let’s explore some sophisticated implementations that demonstrate the full potential of contact integration in Chrome extensions.

Building a Contact Sync Extension

One powerful use case is creating an extension that syncs selected contacts to third-party services or local applications. For example, you could build an extension that allows users to select contacts from their Chrome address book and automatically add them to a CRM system, email marketing platform, or personal database. This requires careful API integration but provides significant value for users who work with multiple contact management systems.

Creating a Contact-Based Workflow Automation Tool

Another advanced application is building workflow automation extensions that use contacts as triggers or recipients. For instance, you could create an extension that monitors for specific events and automatically notifies selected contacts via email or messaging platforms. The Contact Picker API enables users to configure these automated workflows by simply selecting from their existing address book in Chrome rather than manually entering contact information.


Conclusion

The Contact Picker API in Chrome extensions represents a significant opportunity for developers to create contact-aware applications that enhance users’ productivity and streamline communication workflows. By providing a privacy-preserving mechanism for accessing contact information, Chrome enables developers to build powerful extensions while respecting user control over personal data.

This comprehensive guide has covered the essential aspects of implementing the Contact Picker API, from understanding the underlying concepts and configuring manifest permissions to building complete, production-ready extensions with proper error handling and security practices. The Contact Picker API opens doors to numerous applications, from simple contact selection tools to complex contact management and synchronization systems.

As you continue developing extensions that leverage the Contact Picker API, remember to prioritize user privacy, maintain transparency about data usage, and always request only the minimum contact information necessary for your extension’s functionality. By following these principles and the implementation patterns demonstrated in this guide, you’ll be well-equipped to create contact picker extensions that users trust and find valuable for managing their address book contacts in Chrome.

No previous article
No next article