
Security News
Agent Baseline Publishes Security Reference Architecture for Software Factories
Agent Baseline’s new reference architecture shows how to secure software factories with four boundaries enforced outside the AI agent.
@capacitor/contacts
Advanced tools
Access, search, pick, create, update and remove device contacts. Not available on web.
To use npm
npm install @capacitor/contacts
To use yarn
yarn add @capacitor/contacts
Sync native files
npx cap sync
Add 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:
CNContactStore,
never the deprecated AddressBook APIs), so it fully supports iOS 18+
Limited Access: find, save and remove operate on the subset the
user shared with the app, and pickContact presents the system picker
(which needs no permission and always shows the full contact list).note field is not supported on iOS: reading or writing it requires
Apple's restricted com.apple.developer.contacts.notes entitlement. The
field is omitted on read and ignored on save.ids are opaque CNContact identifiers; 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'
FAQs
Access, search, pick, create, update and remove device contacts.
The npm package @capacitor/contacts receives a total of 1,072 weekly downloads. As such, @capacitor/contacts popularity was classified as popular.
We found that @capacitor/contacts demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 18 open source maintainers collaborating on the project.

Security News
Agent Baseline’s new reference architecture shows how to secure software factories with four boundaries enforced outside the AI agent.

Security News
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.

Research
/Security News
A new GhostAction wave hits hundreds of GitHub repos, expanding CI/CD secret theft to cloud and AI credentials in source code and git history.