This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 54).
Expo Contacts (next)
A library that provides access to the phone's system contacts.
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
- npx expo install expo-contactsIf 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
{ "expo": { "plugins": [ [ "expo-contacts", { "contactsPermission": "Allow $(PRODUCT_NAME) to access your contacts." } ] ] } }
Configurable properties
| Name | Default | Description |
|---|---|---|
contactsPermission | "Allow $(PRODUCT_NAME) to access your contacts" | Only for: iOS A string to set the |
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_CONTACTSandandroid.permission.WRITE_CONTACTSpermissions 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
NSContactsUsageDescriptionkey to your project's ios/[app]/Info.plist:<key>NSContactsUsageDescription</key> <string>Allow $(PRODUCT_NAME) to access your contacts</string>
Usage
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
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
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
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 Permission | Description |
|---|---|
Allows an application to read the user's contacts data. | |
Allows an application to write the user's contacts data. |
iOS
The following usage description keys are used by this library: