Contacts for NativePHP Mobile#
Read, create & sync the device address book — straight from Laravel.#
A complete contacts API for your NativePHP mobile app. Query contacts with a fluent builder, create and update entries, manage groups, and handle iOS 18's new limited-access contact picker — all without writing a line of Swift or Kotlin.
Why this plugin?#
- 🔎 Fluent contact search —
Contacts::contacts()->search('John')->withPhoneNumber()->limit(50)->get(). - ✍️ Full CRUD — create, update and delete contacts and groups with typed data objects.
- 🆕 iOS 18 contact access picker — first-class support for Apple's new limited-access flow.
- 📡 Live events —
ContactCreated,ContactUpdated,ContactAccessUpdateddispatched straight into your Livewire components. - 📱 One API, both platforms — iOS Contacts framework and Android ContactsContract, unified.
Features at a glance#
| Feature | Android | iOS |
|---|---|---|
| Read / search contacts | ✅ | ✅ |
| Create / update / delete contacts | ✅ | ✅ |
| Groups | ✅ | ✅ |
| Limited-access contact picker | — | iOS 18+ |
| Real-time events | ✅ | ✅ |
Perfect for#
CRMs · Sales & outreach tools · Messaging & social apps · Dialer / VoIP · Event & conference apps · Field-service & delivery
Installation#
composer require srwiez/nativephp-mobile-contactsphp artisan vendor:publish --tag=nativephp-plugins-providerphp artisan native:plugin:register srwiez/nativephp-mobile-contacts
Usage#
use SRWieZ\NativePHP\Mobile\Contacts\Facades\Contacts;use SRWieZ\NativePHP\Mobile\Contacts\Data\ContactData;use SRWieZ\NativePHP\Mobile\Contacts\Data\PhoneNumber;use SRWieZ\NativePHP\Mobile\Contacts\Data\EmailAddress; // Request permission$result = Contacts::requestAccess(); // Query contacts$contacts = Contacts::contacts() ->search('John') ->withPhoneNumber() ->limit(50) ->get(); // Create a contact$contact = Contacts::createContact( ContactData::make() ->name('John', 'Doe') ->organization('Acme Inc') ->addPhone(PhoneNumber::mobile('+1234567890'))); // Update a contact$contact->update( ContactData::make()->jobTitle('Senior Developer')); // Delete a contact$contact->delete();
Updating Contacts: Merge Semantics#
updateContact() only touches the fields you pass — everything else is left
alone. Two consequences worth knowing:
- List fields replace, not append. Passing
phoneNumbersreplaces all of the contact's phone numbers with the ones you send. Same foremailAddresses,postalAddresses,urlAddresses,datesandrelations. - Clearing a field needs an explicit value. The fluent
ContactDatabuilder omits everything you did not set, so it can never clear a field. To clear one, pass a raw array with an explicit empty value:
// Remove all phone numbersContacts::updateContact($contactId, ['phoneNumbers' => []]); // Clear the nicknameContacts::updateContact($contactId, ['nickname' => '']);
Manage Contact Access#
Open the iOS 18+ contact access picker to let users manage which contacts your app can access in limited-access mode. The call is asynchronous — it returns immediately and dispatches a ContactAccessUpdated event when the user finishes.
use SRWieZ\NativePHP\Mobile\Contacts\Facades\Contacts; $success = Contacts::manageContactAccess(); // true if picker was presented
Platform behavior:
- iOS 18+ — Opens
contactAccessPicker, allowing the user to select multiple contacts in limited-access mode. - iOS < 18 — Returns
falsewith an error (Contact access picker requires iOS 18.0 or later). - Android — Not available (iOS-only feature).
Warning
Known iOS bug: the picker opens blank on iOS 26 and later. On real devices, Apple's contact access picker shows an empty sheet, even in Apple's own sample project. It is reported on every release from iOS 26.0 up to iOS 27.0.1. Apple has acknowledged it as a known problem and has published no fix or workaround (FB20929400, FB21812568). iOS 18 devices and the Simulator are not affected. Until Apple fixes it, send users to your app's page in the Settings app to change which contacts they share.
Apple Developer Forums: contactAccessPicker shows blank sheet on iOS 26.1 · contactAccessPicker shows blank sheet on iOS 26.2.1 on device
JavaScript:
import { mobileContacts } from '../../vendor/srwiez/nativephp-mobile-contacts/resources/js/MobileContacts.js'; await mobileContacts.manageContactAccess();
Checking Availability#
PHP-only checks tell you what the running app provides. Both return false
outside a NativePHP app (tests, web preview) and have no JavaScript equivalent.
Contacts::isAvailable(); // the plugin's native side is installedContacts::canManageAccess(); // the access picker exists (iOS only) if (Contacts::canManageAccess()) { Contacts::manageContactAccess();}
canManageAccess() is false on Android. On iOS 17 it is true but the picker
itself needs iOS 18, so manageContactAccess() still returns false there. On
NativePHP v4 you can test the fallback path with
FakeBridge::enable()->withoutCapability('MobileContacts.ManageContactAccess').
Contact Notes (iOS Restriction)#
Apple locks the contact note field behind a special entitlement
(com.apple.developer.contacts.notes) that must be requested from Apple and is
rarely granted.
- Without the entitlement (the default): creating or updating a contact with
a note on iOS saves everything except the note, and the bridge response
includes a
warning. Reading a contact on iOS never returns its note. - With the entitlement: notes save and behave normally.
- Android: notes work with no restrictions.
Events#
The plugin dispatches these events for asynchronous results. All of them live
in the SRWieZ\NativePHP\Mobile\Contacts\Events namespace and timestamps are
Unix seconds.
| Event | Fired when | Payload keys |
|---|---|---|
ContactAccessGranted |
The user granted access from the permission dialog | status (full_access, limited), timestamp |
ContactAccessDenied |
The user denied access from the permission dialog | reason, timestamp |
ContactAccessUpdated |
The iOS 18+ access picker closed (manageContactAccess()) |
identifiers, contacts, count, timestamp |
ContactCreated |
createContact() saved a contact |
contactId, timestamp |
ContactUpdated |
updateContact() saved a contact, or the user edited one in the iOS contact editor |
contactId, timestamp |
ContactDeleted |
deleteContact() removed a contact |
contactId, timestamp |
GroupCreated |
createGroup() saved a group |
groupId, name, timestamp |
GroupDeleted |
deleteGroup() removed a group |
groupId, timestamp |
Pick the listener style that matches where your code runs. Handler parameters are bound by payload key name, so name them after the keys above.
NativePHP v4 native screens. Put Native\Mobile\Attributes\On on a method of
a Native\Mobile\Edge\NativeComponent and pass the event class. The attribute adds
the native: prefix for you.
use Native\Mobile\Attributes\On;use Native\Mobile\Edge\NativeComponent;use SRWieZ\NativePHP\Mobile\Contacts\Events\ContactAccessUpdated; class AddressBookScreen extends NativeComponent{ #[On(ContactAccessUpdated::class)] public function accessUpdated(array $identifiers, array $contacts = [], int $count = 0): void { // $identifiers: native ids the user selected // $contacts: full contact data for each of them }}
Web view screens with Livewire. Use Livewire's own On attribute with the
native: prefix in front of the event class.
use Livewire\Attributes\On;use Livewire\Component;use SRWieZ\NativePHP\Mobile\Contacts\Events\ContactCreated; class AddressBookScreen extends Component{ #[On('native:'.ContactCreated::class)] public function contactCreated(string $contactId, ?int $timestamp = null): void { // ... }}
Plain Laravel listeners. In web view apps, Event::listen receives the
event object. It is not tied to a screen, so use it for side effects such as
syncing or logging. On NativePHP 4.6.1+ and 3.3.11+ this needs plugin 2.1.0 or
later. On v4 native screens, plugin events only reach the screen's #[On]
handlers.
use Illuminate\Support\Facades\Event;use SRWieZ\NativePHP\Mobile\Contacts\Events\ContactCreated; Event::listen(function (ContactCreated $event) { logger()->info('Contact created', ['id' => $event->contactId]);});
ContactAccessGranted::$status is a PermissionStatus enum on the event
object; in handler methods the $status parameter receives the payload string
such as full_access.
Note
#[OnNative(ContactCreated::class)] (Native\Mobile\Attributes\OnNative) is
the pre-v4 form. It still works wherever Livewire is installed, but it
extends Livewire's attribute, so a v4 app without Livewire never reads it.
Version Support#
| Platform | Minimum Version |
|---|---|
| Android | 5.0 (API 21) |
| iOS | 17.0 |
Features requiring higher versions:
- iOS 18+:
manageContactAccess()contact access picker for limited-access mode
NativePHP Mobile v4 apps themselves require Android 8.0 (API 26) or newer and target iOS 18.2, so on v4 those are the effective minimums.
Support#
Bugs, questions, and feature requests should be reported at github.com/SRWieZ/nativephp-mobile-packages.