This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 54).

Expo Contacts (next) iconExpo Contacts (next)

A library that provides access to the phone's system contacts.

Android
iOS
Included in Expo Go

expo-contacts provides access to the device's system contacts, allowing you to get contact information as well as add, edit, or remove contacts. On iOS, contacts have a multi-layered grouping system that you can also access through this API.

Installation

Terminal
npx expo install expo-contacts

If you are installing this in an existing React Native app, make sure to install expo in your project.

Configuration in app config

You can configure expo-contacts using its built-in config plugin if you use config plugins in your project (Continuous Native Generation (CNG)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect. If your app does not use CNG, then you'll need to manually configure the library.

Example app.json with config plugin

app.json
{ "expo": { "plugins": [ [ "expo-contacts", { "contactsPermission": "Allow $(PRODUCT_NAME) to access your contacts." } ] ] } }

Configurable properties

NameDefaultDescription
contactsPermission"Allow $(PRODUCT_NAME) to access your contacts"
Only for:
iOS

A string to set the NSContactsUsageDescription permission message.

Are you using this library in an existing React Native app?

If you're not using Continuous Native Generation (CNG) (you're using native android and ios projects manually), then you need to configure following permissions in your native projects:

  • For Android, add android.permission.READ_CONTACTS and android.permission.WRITE_CONTACTS permissions to your project's android/app/src/main/AndroidManifest.xml:

    <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
  • For iOS, add the NSContactsUsageDescription key to your project's ios/[app]/Info.plist:

    <key>NSContactsUsageDescription</key> <string>Allow $(PRODUCT_NAME) to access your contacts</string>

Usage

Contacts Manipulations
Contacts Manipulations
const contact = await Contact.create({ givenName: 'John', familyName: 'Doe' }); // { givenName: "John", familyName: "Doe"} await contact.setGivenName('Andrew'); // { givenName: "Andrew", familyName: "Doe"} await contact.addPhone({ label: 'work', number: '+12345678912' }); // { givenName: "Andrew", familyName: "Doe", phones: [{label: "work", number: "+12345678912"}]} const phones = await contact.getPhones(); // Changes only the defined fields, leaves the rest unchanged await contact.patch({ phones: [...phones, { label: 'home', number: '+98765432198' }] }); /* { givenName: "Andrew", familyName: "Doe", phones: [ {label: "work", number: "+12345678912"}, {label: "home", number: "+98765432198"} ] } */ // Replaces all fields with the ones defined in the object await contact.update({ givenName: 'John', familyName: 'Doe' }); // { givenName: "John", familyName: "Doe"}
Retrieving contacts
Retrieving contacts
const contactDetails = await Contact.getAllDetails([ContactField.FULL_NAME, ContactField.PHONES], { limit: 20, offset: 10, sortOrder: ContactsSortOrder.GivenName, }); // Contact instance can be created from fetched details const contacts = contactDetails.map(item => new Contact(item.id)); const contactsFromGetAll = await Contact.getAll({ limit: 20, offset: 10, sortOrder: ContactsSortOrder.GivenName, });
Contacts infinite scroll example
Contacts infinite scroll example
import { Contact, ContactField, PartialContactDetails } from 'expo-contacts/next'; import { useEffect, useState } from 'react'; import { FlatList, Text, View } from 'react-native'; const FIELDS = [ContactField.FULL_NAME, ContactField.PHONES] as const; export default function InfiniteContacts() { const [contactDetails, setContactDetails] = useState<PartialContactDetails<typeof FIELDS>[]>([]); useEffect(() => { loadMore(); }, []); const loadMore = async () => { const newBatch = await Contact.getAllDetails(FIELDS, { limit: 20, offset: contactDetails.length, }); setContactDetails(prev => [...prev, ...newBatch]); }; return ( <View style={{ flex: 1 }}> <FlatList data={contactDetails} keyExtractor={item => item.id} onEndReached={loadMore} onEndReachedThreshold={0.5} renderItem={({ item }) => ( <View style={{ padding: 10, borderBottomWidth: 1, borderColor: '#ccc' }}> <Text>{item.fullName}</Text> <Text>{item.phones[0]?.number ?? 'No phone number'}</Text> </View> )} /> </View> ); }
Edit contact form example
Edit contact form example
import { Contact, ContactField, ContactPatch } from 'expo-contacts/next'; import { useEffect, useState } from 'react'; import { Alert, Button, ScrollView, Text, TextInput, View } from 'react-native'; export default function ContactForm() { const [contact, setContact] = useState<Contact | null>(null); const [contactPatch, setContactPatch] = useState<ContactPatch>({}); const [newPhoneInput, setNewPhoneInput] = useState(''); useEffect(() => { Contact.getAll({ limit: 1 }).then(async ([first]) => { if (first) { setContact(first); setContactPatch(await first.getDetails([ContactField.GIVEN_NAME, ContactField.PHONES])); } }); }, []); if (!contactPatch && contact) { return <Text style={{ marginTop: 50 }}>Loading details...</Text>; } const handleChangeName = (text: string) => setContactPatch(prev => ({ ...prev, givenName: text })); const handleAddPhone = () => { if (!newPhoneInput) { return; } setContactPatch(prev => ({ ...prev, phones: [...(prev.phones || []), { label: 'mobile', number: newPhoneInput }], })); setNewPhoneInput(''); }; const handleRemovePhone = (idx: number) => setContactPatch(prev => ({ ...prev, phones: prev.phones?.filter((_, i) => i !== idx), })); const handlePatch = async () => { if (contact) { await contact.patch(contactPatch); } Alert.alert('Contact patched successfully'); }; if (!contact) { return <Text style={{ marginTop: 50 }}>Loading...</Text>; } return ( <ScrollView style={{ padding: 20, paddingTop: 60 }}> <Text style={{ fontWeight: 'bold', marginBottom: 10 }}>Edit ID: {contact.id}</Text> <Text>Name:</Text> <TextInput style={{ borderWidth: 1, padding: 5, marginBottom: 10 }} value={contactPatch.givenName || ''} onChangeText={handleChangeName} /> <Text>Phones:</Text> {contactPatch.phones?.map((phone, index) => ( <View key={index} style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 5 }}> <Text style={{ flex: 1 }}>{phone.number}</Text> <Button title="Remove" onPress={() => handleRemovePhone(index)} /> </View> ))} <View style={{ flexDirection: 'row', marginBottom: 20, marginTop: 10 }}> <TextInput style={{ borderWidth: 1, flex: 1, padding: 5, marginRight: 5 }} value={newPhoneInput} onChangeText={setNewPhoneInput} placeholder="New phone..." /> <Button title="Add" onPress={handleAddPhone} /> </View> <Button title="PATCH CONTACT" onPress={handlePatch} /> </ScrollView> ); }

API

import { Contact } from 'expo-contacts/next';

No API data file found, sorry!

Permissions

Android

This library automatically adds READ_CONTACTS and WRITE_CONTACTS permissions to your app:

Android PermissionDescription

READ_CONTACTS

Allows an application to read the user's contacts data.

WRITE_CONTACTS

Allows an application to write the user's contacts data.

iOS

The following usage description keys are used by this library:

Info.plist KeyDescription

NSContactsUsageDescription

A message that tells the user why the app is requesting access to the user’s contacts.