One.Contacts uses the iOS Contacts store. Set a purpose string before
prebuild so the system can show the permission prompt:
contacts: { usage: 'Find people to invite.' }Expo-prebuilt apps pass the same contacts option to the VXRN Expo plugin.
Both paths set NSContactsUsageDescription.
import { One } from 'one'
const permission = await One.Contacts.requestPermission()if (permission === 'authorized' || permission === 'limited') { const matches = await One.Contacts.search('Ada', 20) console.log(matches.map(({ givenName, familyName }) => `${givenName} ${familyName}`))}getPermissionStatus() reads without prompting. The status can be
notDetermined, restricted, denied, authorized, or limited. Limited
access returns only contacts the person has shared with the app.
pickContact() opens Apple’s system contact picker and resolves with the
selected contact in the same shape as search, or undefined when the person
cancels. The picker exposes only the chosen contact and does not request
Contacts permission. Call it from a visible screen; a concurrent picker or
missing presentation surface rejects with E_CONTACTS_PICKER.
search(name, limit = 100) returns at most 100 matching contacts with an
identifier, given and family names, phone number strings, email address
strings, and structured postal addresses. It requires a nonempty name and a whole-number limit from 1 to 100.
create({ givenName, familyName, phoneNumbers, emailAddresses, postalAddresses }) saves a
contact and returns its identifier. update(identifier, changes) edits any
combination of these fields and returns the updated contact. Omitted fields
keep their values. Supplied phone and email arrays replace their whole field;
entries with the same value keep their existing iOS labels, while new entries
use the default label. An empty array clears that field. Blank phone and email
entries are rejected by both create and update. A contact
must keep at least one given or family name if either name is changed.
postalAddresses is optional at create time and replaces the whole address
field when supplied to update. Each address accepts label, street,
subLocality, city, subAdministrativeArea, state, postalCode, country,
and isoCountryCode; at least one address part must be nonblank. Search and
update return all these parts and the raw iOS label. An omitted label uses
the iOS home label for a new address. On update, an unchanged value with an
omitted or unchanged label keeps its native label and identifier. An empty
array clears addresses.
delete(identifier) removes a contact the app is allowed to change. Ask the
person before changing their contacts. Search and writes run away from the UI
thread.
Missing usage text rejects with E_CONTACTS_MANIFEST; missing access with
E_CONTACTS_PERMISSION; invalid input with E_CONTACTS_INPUT. Editing an
unknown identifier on update or delete rejects with E_CONTACTS_NOT_FOUND. Store failures use
E_CONTACTS_FETCH, E_CONTACTS_SAVE, or E_CONTACTS_DELETE. Contacts are implemented on iOS and Android. On Android, the address book runs over ContactsContract with native.app.contacts.usage; there is no limited tier. Web and SSR return denied address-book permission and empty searches. Picker and save calls that return a contact reject with Contacts.<verb> needs an iOS or Android build where unimplemented; deletion does nothing there. Contact notes require an Apple-granted
entitlement and are not exposed.
See Apple’s Contacts access guide for how full and limited access differ.
On browsers with the Contact Picker API, pickContact() opens the system picker
from a user gesture and returns one selected contact. Cancellation returns
undefined. The picker grants access to that selection only; it does not grant
address-book permission. The display name is returned as givenName, with an
empty family name and identifier because the browser exposes neither separate
name fields nor a stable contact identifier. Phone, email, and postal fields are
requested only when supported. Search, create, update, and delete retain their
unavailable contracts. Browsers without Contact Picker keep the existing
Contacts.pickContact rejection.
Edit this page on GitHub.