Access, search, pick, create, update and remove device contacts.
To use npm
npm install @capacitor/contactsTo use yarn
yarn add @capacitor/contactsSync native files
npx cap syncAdd the NSContactsUsageDescription key to your app's Info.plist and
describe why the app accesses the user's contacts; iOS crashes on first
contacts access without it:
<key>NSContactsUsageDescription</key>
<string>We need access to contacts to search, save and remove them.</string>Notes:
- The plugin uses the modern Contacts framework (
CNContactStore, never the deprecated AddressBook APIs), so it fully supports iOS 18+ Limited Access:find,saveandremoveoperate on the subset the user shared with the app, andpickContactpresents the system picker (which needs no permission and always shows the full contact list). - The
notefield is not supported on iOS: reading or writing it requires Apple's restrictedcom.apple.developer.contacts.notesentitlement. The field is omitted on read and ignored on save. - Contact
ids are opaqueCNContactidentifiers; ids persisted by the legacy AddressBook-based Cordova plugin do not resolve after migration.
The plugin declares READ_CONTACTS and WRITE_CONTACTS in its own
manifest; Gradle manifest merging adds them to your app automatically. Each
method requests the runtime permission it needs the first time it runs;
read for find/pickContact, read + write for save/remove.
This plugin intentionally exposes no checkPermissions() /
requestPermissions() methods: permissions are requested implicitly by each
method, matching the legacy cordova-plugin-contacts behavior. A call
rejects with OS-PLUG-CONT-0020 when the user denies access.
Every rejection carries a structured code + message:
| Code | Meaning |
|---|---|
OS-PLUG-CONT-0000 |
Unknown error |
OS-PLUG-CONT-0001 |
Invalid argument (e.g. unknown id) |
OS-PLUG-CONT-0002 |
Timeout (reserved, currently unused) |
OS-PLUG-CONT-0003 |
Pending operation (e.g. picker already open) |
OS-PLUG-CONT-0004 |
I/O error |
OS-PLUG-CONT-0005 |
Not supported |
OS-PLUG-CONT-0006 |
Operation cancelled (picker closed) |
OS-PLUG-CONT-0020 |
Permission denied |
import { Contacts } from '@capacitor/contacts';
// Search every field for "ada" and return all matches with a phone number
const { contacts } = await Contacts.find({
fields: ['*'],
filter: 'ada',
multiple: true,
hasPhoneNumber: true,
});
// Create a contact
const saved = await Contacts.save({
contact: {
name: { givenName: 'Ada', familyName: 'Lovelace' },
phoneNumbers: [{ type: 'mobile', value: '+351910000000' }],
emails: [{ type: 'home', value: 'ada@example.com' }],
},
});
// Update it (id present -> update)
await Contacts.save({ contact: { ...saved, nickname: 'Countess' } });
// Pick a contact with the native picker
const picked = await Contacts.pickContact();
// Remove it by id
await Contacts.remove({ id: saved.id! });find(options: ContactFindOptions) => Promise<ContactFindResult>Queries the device contacts database and returns the matching contacts.
Requests the READ_CONTACTS (Android) / Contacts (iOS) permission internally the first time it runs; there is no separate permission method.
iOS 18+: under Limited Access the search runs against (and returns only) the subset of contacts the user shared with the app.
| Param | Type |
|---|---|
options |
ContactFindOptions |
Returns: Promise<ContactFindResult>
Since: 1.0.0
save(options: ContactSaveOptions) => Promise<Contact>Persists a new contact or updates an existing one (matched by contact.id).
Resolves with the full saved contact.
Requests the READ/WRITE_CONTACTS (Android) / Contacts (iOS) permission internally.
iOS 18+: works under Limited Access: new contacts are added to the
app's accessible set; updating requires the target contact to be in that
set (otherwise the call rejects with OS-PLUG-CONT-0001).
| Param | Type |
|---|---|
options |
ContactSaveOptions |
Returns: Promise<Contact>
Since: 1.0.0
remove(options: ContactRemoveOptions) => Promise<void>Removes the contact with the given id from the device contacts database.
Rejects with OS-PLUG-CONT-0001 when no contact has that id (on iOS 18+
Limited Access, also when the contact is outside the accessible set).
Requests the READ/WRITE_CONTACTS (Android) / Contacts (iOS) permission internally.
| Param | Type |
|---|---|
options |
ContactRemoveOptions |
Since: 1.0.0
pickContact() => Promise<Contact>Launches the native contact picker UI and resolves with the contact the
user selects. Rejects with OS-PLUG-CONT-0006 if the user cancels.
On iOS the system picker requires no permission and always shows the full contact list, even under iOS 18+ Limited Access (the picked contact is returned without joining the app's accessible set). On Android the READ_CONTACTS permission is requested internally to read the picked contact's details.
Returns: Promise<Contact>
Since: 1.0.0
Result of a {@link ContactsPlugin.find} call.
| Prop | Type | Description | Since |
|---|---|---|---|
contacts |
Contact[] |
The contacts matching the search criteria. | 1.0.0 |
A single device contact.
| Prop | Type | Description | Since |
|---|---|---|---|
id |
string |
Globally unique, platform-assigned identifier. Absent for contacts not yet saved to the device. | 1.0.0 |
rawId |
string |
Android raw-contact id backing this aggregated contact. iOS leaves this unset. | 1.0.0 |
displayName |
string |
Name suitable for display to end users. | 1.0.0 |
name |
ContactName |
The structured name components. | 1.0.0 |
nickname |
string |
A casual name by which to address the contact. | 1.0.0 |
phoneNumbers |
ContactField[] |
The contact's phone numbers. | 1.0.0 |
emails |
ContactField[] |
The contact's email addresses. | 1.0.0 |
addresses |
ContactAddress[] |
The contact's postal addresses. | 1.0.0 |
ims |
ContactField[] |
The contact's instant-messaging handles. | 1.0.0 |
organizations |
ContactOrganization[] |
The contact's organizations. | 1.0.0 |
birthday |
number |
The contact's birthday as epoch milliseconds. | 1.0.0 |
note |
string |
A free-form note about the contact. iOS: not supported by default: reading/writing a contact's note requires Apple's restricted com.apple.developer.contacts.notes entitlement. Without it the field is omitted on read and ignored on save. Android has no such restriction. |
1.0.0 |
photos |
ContactField[] |
The contact's photos. Reads return type: 'url' with the value holding a reference to the image, never image bytes: on Android the contact's content:// photo URI, on iOS the path of a copy written to the app's temporary directory (cleared by the system). On save, the first entry is applied: pass type: 'base64' with base64 data, or type: 'url' with a local file:///content:// URI to import. |
1.0.0 |
categories |
ContactField[] |
User-defined categories associated with the contact. Read-only: populated from the contact's group memberships on Android, never returned on iOS (the Contacts framework has no equivalent), and ignored on save. | 1.0.0 |
urls |
ContactField[] |
Web pages associated with the contact. | 1.0.0 |
Structured name of a {@link Contact}.
| Prop | Type | Description | Since |
|---|---|---|---|
formatted |
string |
The complete formatted name. | 1.0.0 |
familyName |
string |
Family (last) name. | 1.0.0 |
givenName |
string |
Given (first) name. | 1.0.0 |
middleName |
string |
Middle name. | 1.0.0 |
honorificPrefix |
string |
Honorific prefix (e.g. Mr., Dr.). |
1.0.0 |
honorificSuffix |
string |
Honorific suffix (e.g. Esq.). |
1.0.0 |
A generic, repeatable contact field (phone number, email, IM, photo, URL, category).
| Prop | Type | Description | Since |
|---|---|---|---|
type |
string |
The kind of field, e.g. home, work, mobile. For photos, url or base64. |
1.0.0 |
value |
string |
The field value (phone number, email address, URI, etc.). | 1.0.0 |
pref |
boolean |
true if this is the contact's preferred value for the field. |
1.0.0 |
id |
string |
Platform-assigned id of this individual field entry. | 1.0.0 |
A postal address of a {@link Contact}.
| Prop | Type | Description | Since |
|---|---|---|---|
id |
string |
Platform-assigned id of this address entry. | 1.0.0 |
pref |
boolean |
true if this is the contact's preferred address. |
1.0.0 |
type |
string |
The kind of address, e.g. home, work. |
1.0.0 |
formatted |
string |
The full address formatted for display. | 1.0.0 |
streetAddress |
string |
The street address. | 1.0.0 |
locality |
string |
The city or locality. | 1.0.0 |
region |
string |
The state or region. | 1.0.0 |
postalCode |
string |
The ZIP or postal code. | 1.0.0 |
country |
string |
The country name. | 1.0.0 |
An organization a {@link Contact} belongs to.
| Prop | Type | Description | Since |
|---|---|---|---|
id |
string |
Platform-assigned id of this organization entry. Android only; iOS models the organization as flat contact properties without an id. | 1.0.0 |
pref |
boolean |
true if this is the contact's preferred organization. |
1.0.0 |
type |
string |
The kind of organization, e.g. work. |
1.0.0 |
name |
string |
The organization name. | 1.0.0 |
department |
string |
The department within the organization. | 1.0.0 |
title |
string |
The contact's title at the organization. | 1.0.0 |
Search options accepted by {@link ContactsPlugin.find}.
| Prop | Type | Description | Since |
|---|---|---|---|
fields |
ContactFieldType[] |
Fields to search against. Pass ['*'] to match every field. An empty array is invalid and rejects with OS-PLUG-CONT-0001. The id field matches by exact identifier; all other fields match case-insensitive substrings. photos and categories are not searchable. |
1.0.0 |
filter |
string |
Search string matched (case-insensitively) against the selected fields. An empty/omitted filter returns every contact. |
1.0.0 |
multiple |
boolean |
When true, returns every match; when false (default), returns at most one contact. |
1.0.0 |
desiredFields |
ContactFieldType[] |
If set, each returned {@link Contact} only includes these fields (plus the always-present id). |
1.0.0 |
hasPhoneNumber |
boolean |
OutSystems extension: when true, only contacts that have at least one phone number are returned. Defaults to false. |
1.0.0 |
Options accepted by {@link ContactsPlugin.save}.
| Prop | Type | Description | Since |
|---|---|---|---|
contact |
Contact |
The contact to create (no id) or update (existing id). Update semantics: every field present on the contact replaces the stored value entirely (e.g. name replaces the whole structured name, phoneNumbers replaces all phone numbers); omitted fields are left unchanged. |
1.0.0 |
Options accepted by {@link ContactsPlugin.remove}.
| Prop | Type | Description | Since |
|---|---|---|---|
id |
string |
The native id of the contact to remove. | 1.0.0 |
The set of contact fields a {@link ContactsPlugin.find} call can search
against or request back. Mirrors the legacy Cordova ContactFieldType
string values exactly.
'addresses' | 'birthday' | 'categories' | 'country' | 'department' | 'displayName' | 'emails' | 'familyName' | 'formatted' | 'givenName' | 'honorificPrefix' | 'honorificSuffix' | 'id' | 'ims' | 'locality' | 'middleName' | 'name' | 'nickname' | 'note' | 'organizations' | 'phoneNumbers' | 'photos' | 'postalCode' | 'region' | 'streetAddress' | 'title' | 'urls'