Reports whether the host app is configured for the contacts capabilities that read the address book, so an eitri-app can degrade gracefully instead of hitting a failure.
NSContactsUsageDescription in its Info.plist.
Without that key CNContactStore access cannot be requested at all.READ_CONTACTS or WRITE_CONTACTS in its manifest.
An undeclared permission is silently denied without ever prompting the user. Either one
counts, since a read-only host declares only READ_CONTACTS; use
checkPermission with an access scope to tell read and write apart.pickContact is the exception on both platforms: the OS renders the picker and
returns only the chosen contact, so it needs neither the iOS usage-description key nor a
declared Android permission and stays usable when this resolves false.
Resolves false when the contacts module is not installed at all, so a missing integration is
reported as unsupported rather than throwing.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Opens a stream of the device's entire contact list and resolves with the channel name to subscribe to via EventBus for the resulting ContactStreamChunk batches.
Native does not begin publishing until you call startContactsStream. The
required sequence is: (1) call this method to get dataChannel, (2) subscribe to that channel,
(3) call startContactsStream({ dataChannel }). Starting only after subscribing guarantees no
chunk is lost to a publish-before-subscribe race.
Each event is a ContactStreamChunk: data-bearing chunks carry final: false and a
data array of Contact; a terminal chunk with final: true (and no data) closes the
stream. On failure, a single chunk with success: false, final: true and an errorMessage
is emitted instead — including "contacts.stream.not.started" when
startContactsStream is not called within 30 seconds. Every path therefore ends
in a final: true chunk, so a subscriber never waits indefinitely. Remember to clear the
subscription once the stream ends.
Example:
const modules = await Eitri.modules();
if (!modules?.contacts?.streamAllContacts) {
return; // contacts module is unavailable; hide UI that depends on it
}
const permission = await Eitri.contacts.checkPermission({ access: "read" });
if (permission.status !== "GRANTED") {
const granted = await Eitri.contacts.requestPermission({ access: "read" });
if (granted.status !== "GRANTED") {
return; // permission refused
}
}
const allContacts = [];
// 1. Open the stream (native pauses before reading contacts)
const { dataChannel } = await Eitri.contacts.streamAllContacts();
// 2. Subscribe to the channel
const onChunk = (chunk) => {
if (!chunk.success) {
console.log("contacts.stream.error:", chunk.errorMessage);
Eitri.eventBus.clear({ channel: dataChannel, callback: onChunk });
return;
}
if (chunk.data) {
allContacts.push(...chunk.data);
}
if (chunk.final) {
Eitri.eventBus.clear({ channel: dataChannel, callback: onChunk });
console.log("received", allContacts.length, "contacts");
}
};
Eitri.eventBus.subscribe({ channel: dataChannel, callback: onChunk });
// 3. Start streaming (safe — the listener is already registered)
await Eitri.contacts.startContactsStream({ dataChannel });
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Signals the native side to begin streaming contacts for a channel opened by streamAllContacts. Must be called after subscribing to that channel via EventBus.
This exists to avoid a race where native starts publishing chunks before the eitri-app has
registered its listener — chunks published to a channel with no listener are dropped. Native
holds the stream until this call arrives, for up to 30 seconds; if it never arrives, the stream
closes with a terminal success: false chunk carrying errorMessage: "contacts.stream.not.started"
rather than leaving a subscriber waiting forever. Calling this after that point is a no-op.
See streamAllContacts for the full open → subscribe → start example.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
The dataChannel returned by streamAllContacts.
Returns the current status of contacts access without prompting the user, scoped to the
requested capability. Read and write are independent: checking "read" never reports blocked
because write is unavailable, and checking "write" never reports blocked because read is
unavailable.
READ_CONTACTS for "read", WRITE_CONTACTS for "write", or both
for "readWrite". Returns BLOCKED before the first request, DENIED after a rejection
that can still be retried, and GRANTED once the scoped permission(s) are allowed.CNContactStore authorization (a single read+write grant), so every scope
maps to the same status: DENIED when not yet determined, BLOCKED when denied or
restricted, and GRANTED once authorized.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Which capability to check. access is required — pass the narrowest scope the
eitri-app actually needs.
Prompts the user for contacts access, if not already granted, scoped to the requested capability. Read and write are independent — request only what the eitri-app needs.
READ_CONTACTS ("read"), WRITE_CONTACTS
("write"), or both ("readWrite"), and resolves with the resulting status.CNContactStore access (one authorization covering read and write) for
every scope, and resolves with the resulting status. A host that has not declared
NSContactsUsageDescription resolves BLOCKED without ever reaching the native request,
since making that request would terminate the process.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Which capability to request. access is required — request only what the
eitri-app actually needs.
Stops a running contacts stream, so the native side abandons a read the eitri-app no longer needs — navigating away from the screen that opened it, or the user cancelling. Chunks already delivered stay delivered; no further chunk arrives, including a terminal one.
Safe to call for an unknown or already-finished dataChannel: it resolves without effect.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
The dataChannel returned by streamAllContacts.
Opens the native system contact card for the contact identified by id,
so the user can view (and, through the OS UI, edit) it. Resolves once the card has been
presented. Use the id you obtained from streamAllContacts or
pickContact.
ACTION_VIEW on the contact's lookup URI. The OS Contacts app renders
the card, so no contacts permission is required.CNContactViewController, which must be handed a fully-fetched contact.
That read goes through CNContactStore and therefore requires contacts authorization.This asymmetry matters when pairing with pickContact: on iOS the picker grants
only a one-time snapshot of the chosen contact, never lasting access to it, so an id kept from
the picker cannot be reopened later without authorization — the call rejects with a permission
error. Android has no such restriction. To support both platforms, either check
checkPermission with { access: "read" } before offering an "open contact"
action, or render the picked contact's data in your own UI instead of opening the system card.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Opens the native system contact picker so the user can choose a single contact, and
resolves with the chosen Contact (including its id), or null if
the user dismisses the picker without choosing.
This is the module picker. pickContactBasic is the built-in alternative that needs no module at all, at the cost of a much thinner contact on Android — compare the two before choosing.
This is an alternative to streamAllContacts for getting a contact's data: the OS renders the list/selection UI and only the picked contact is returned, so it works without contacts read permission — with a platform difference in how much data comes back.
ACTION_PICK against the contacts provider. The grant Android issues
covers the returned URI exactly, not the sub-directories holding the data rows, so without
READ_CONTACTS the result carries id and displayName only.
Hold READ_CONTACTS to receive phones, emails, addresses and organization as well; check
checkPermission with { access: "read" } if the eitri-app needs to know
which shape to expect.CNContactPickerViewController, which runs out of process and returns
the fully-populated contact regardless of authorization.The returned Contact carries the data the eitri-app needs, but on iOS the picker is a one-time snapshot rather than a grant: id cannot be used to read that contact again later, so passing it to openContact still requires authorization. Treat the returned object — not the id — as the result of picking.
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Deletes the contact identified by id.
Rejects when the contact no longer exists on either platform, so a stale id from an earlier stream is reported rather than resolving as a delete that never happened.
This is a write operation:
WRITE_CONTACTS permission.CNContactStore write access (the same authorization as reading).
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Creates a new contact from the provided ContactInput. Resolves once the contact has been written, and rejects if the write fails.
This is a write operation:
WRITE_CONTACTS permission.CNContactStore write access (the same authorization as reading).
- Module:
contacts- Install eitri-ios-contacts or eitri-android-contacts
Opens the native system contact picker so the user chooses a single contact, and resolves
with the chosen Contact — or null when the user dismisses the picker without
choosing.
Unlike the rest of this service, this method is built into eitri-machine: it needs no contacts module and no contacts permission, because the OS renders the picker and hands back only what the user explicitly selected. The trade-off is how much of the contact comes back, which differs sharply per platform — see What each platform returns below.
pickContact is the module counterpart: it needs the contacts module installed, but returns the same fully-populated shape on both platforms. Prefer it when the eitri-app needs consistent data; prefer this one when it must work on a host app that ships no contacts module.
| Field | iOS | Android 17+ | Android < 17 |
|---|---|---|---|
| id | ✅ | ✅ | ✅ |
| displayName | ✅ | ✅ | ✅ |
| phones | ✅ all numbers | ✅ all numbers | ⚠️ only the single number picked |
| emails | ✅ | ✅ | ❌ always empty |
| structuredName | ✅ | ❌ always null |
❌ always null |
| addresses | ✅ | ❌ always empty | ❌ always empty |
| organization | ✅ | ❌ always null |
❌ always null |
CNContactPickerViewController, which runs out of process and returns
the whole contact record. Every field above is populated when the contact has it.
normalizedNumber is the one exception: iOS exposes no normalized form,
so it is always null.displayName, postal addresses and
organization are never shared, so those fields come back empty regardless of the contact.READ_CONTACTS.Treat the returned object — not the id — as the result of picking. On iOS the picker is a one-time snapshot rather than a grant, so id cannot be used to read that contact again later; passing it to openContact still requires authorization. Android has no such restriction.
Rejects with contacts.pick.context.isnt.activity when the host app has no screen to present
the picker from, and with contacts.pick.unavailable (Android) when no installed app can
handle the pick. A dismissed picker resolves null — it is never reported as an error.
- API LEVEL
38- Functionality added
Opens the native system contact picker in multi-selection mode and resolves with the chosen contacts, or an empty array when the user dismisses the picker without choosing.
Like pickContactBasic, this is built into eitri-machine and requires no contacts module and no contacts permission.
contacts.pick.multiple.unsupported. Fall back to pickContactBasic,
which works on every Android version and routes to this same picker when it is available.Call isMultiplePickSupported before offering multi-selection in your UI, so an unsupported device never gets a button that can only fail.
Each element carries the same per-platform shape described in pickContactBasic: iOS returns fully-populated contacts, while Android returns id, displayName, phones and emails only — structuredName, addresses and organization are never shared by the picker.
The user may select as many contacts as they wish; neither platform imposes a cap.
Rejects with contacts.pick.context.isnt.activity when the host app has no screen to present
the picker from — a host integration fault, reported rather than resolving as an empty
selection.
- API LEVEL
38- Functionality added
Reports whether this device can present the multi-selection contact picker, so an eitri-app can hide or disable a "pick several contacts" affordance instead of offering one that only fails when tapped.
true.true only on Android 17 or newer, where the system contacts picker
first supports selecting several contacts.Only pickContactsBasic depends on this. pickContactBasic works on every device regardless.
if (await Eitri.contacts.isMultiplePickSupported()) {
const contacts = await Eitri.contacts.pickContactsBasic();
}
- API LEVEL
38- Functionality added
Generated using TypeDoc
Overview
Streams the device's entire contact list to the eitri-app over the EventBus, leaving the UI entirely to the developer. The list is delivered as a sequence of batches rather than a single response to avoid the size limit of one bridge message.
Permissions
READ_CONTACTSpermission.NSContactsUsageDescriptionin itsInfo.plist, otherwise the request fails.