Google Play’s Contacts Permissions policy means that, from January 27, 2027, apps targeting Android 17 (API level 37) or later may only request READ_CONTACTS if the Android Contact Picker cannot support a core feature, and must justify it with a Play Console declaration. For most Capacitor apps that read contacts to invite a friend, share a file or fill in a phone number, the fix is to switch to the Contact Picker and remove the permission. This guide explains the policy, shows how to do that with @capgo/capacitor-contacts, and covers what to do if you really need the full address book.
What the policy says
Google announced the policy on April 15, 2026, as part of a set of privacy updates. The key points, from the Play Console Help pages:
- Scope: apps that target Android 17 (API 37) or higher and request
READ_CONTACTS. - Rule: you may only request
READ_CONTACTSif the Android Contact Picker is not sufficient for a core feature of your app. - Declaration: apps that keep the permission must fill a declaration in Play Console describing which features need it and why the picker is not enough.
- Timeline: Play Console began prompting apps that declare
READ_CONTACTSin September 2026. Compliance is mandatory on January 27, 2027. Google mentions a 30 day self-service extension in Play Console. - Exemptions: private apps and enterprise device management apps are out of scope.
- Data: contacts data is personal and sensitive under the User Data policy, and you may not publish non-public contacts data without authorization from the people it describes.
Use cases that can keep READ_CONTACTS
Google lists core features such as contact management apps, dialers and SMS apps, call history and call screening, CRM, accessibility features, personal assistants, friend matching, backup and restore, and keyboards or autocomplete.
Use cases that should switch to the picker
Inviting or referring someone, sharing files, collaborating, or choosing a contact to send money to. If that is why your app reads contacts, the declaration will not be accepted. Use the picker.
What changes on Android 17
Android 17 ships a new system Contact Picker. Apps get a temporary, read-only session URI that only exposes the contacts and fields the user selected, without any permission. For apps targeting Android 17, the system automatically upgrades the classic Intent.ACTION_PICK contacts intent to this new picker. Apps can also request specific data fields (phone, email, postal address) and multiple selection through a new intent action.
On older Android versions, picking a specific data row (for example a phone number) through the contacts picker has always returned a URI the app can read without READ_CONTACTS. That is what makes a permission-free flow possible on every Android version.
Why “pick a contact” used to need READ_CONTACTS
Many Capacitor plugins implement “pick contact” by opening the picker for a whole contact, then querying the address book for that contact’s phone numbers and emails. That second query needs READ_CONTACTS. So even apps that only wanted one phone number ended up declaring the permission, which is exactly what the policy targets.
The fix is to ask the picker for the specific field you need, so the result URI already carries the data.
Pick a phone, email or address without READ_CONTACTS
@capgo/capacitor-contacts added a property option in version 8.1.0. When set, Android opens the picker against the phone, email or postal address table and reads the selected row from the granted URI. No READ_CONTACTS is needed on any Android version. On iOS, the system picker never needs a contacts permission.
bun add @capgo/capacitor-contacts
bunx cap sync
import { CapacitorContacts, ContactProperty } from '@capgo/capacitor-contacts';
export async function pickPhoneNumber(): Promise<string | undefined> {
const { contacts } = await CapacitorContacts.pickContacts({
property: ContactProperty.PhoneNumber,
});
const picked = contacts[0];
return picked?.phoneNumbers?.[0]?.value;
}
export async function pickEmail(): Promise<string | undefined> {
const { contacts } = await CapacitorContacts.pickContacts({
property: ContactProperty.EmailAddress,
});
return contacts[0]?.emailAddresses?.[0]?.value;
}
With property, the result contains the contact id, displayName and the selected property only. Multiple selection is ignored, property picking always returns one value. If the user cancels, check for an empty array.
The plugin does not declare READ_CONTACTS or WRITE_CONTACTS in its own manifest, so if you only use property picking, your merged manifest stays clean.
When you still need READ_CONTACTS
These plugin methods read the address book and keep requiring the permission:
pickContacts()orpickContact()withoutproperty, when you want the full contact recordgetContacts(),getContactById(),countContacts()getGroups(),getGroupById(),getAccounts()
If your app is a CRM, a backup tool or a friend matching feature, that is a legitimate core use. Add the permission yourself and file the declaration:
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
import { CapacitorContacts } from '@capgo/capacitor-contacts';
const perm = await CapacitorContacts.requestPermissions({ permissions: ['readContacts'] });
if (perm.readContacts === 'granted' || perm.readContacts === 'limited') {
const { contacts } = await CapacitorContacts.getContacts({
fields: ['displayName', 'phoneNumbers'],
limit: 200,
offset: 0,
});
console.log(contacts.length);
}
The policy targets READ_CONTACTS. Writing contacts with WRITE_CONTACTS (for createContact, updateContactById, deleteContactById) is not the subject of this policy, but only add it if you use those methods.
Migration plan for a Capacitor app
-
Audit the merged manifest. Build your release and open
android/app/build/intermediates/merged_manifest/(or use Android Studio’s Merged Manifest tab). Some plugins addREAD_CONTACTSwithout you noticing. If a plugin adds it and you do not need it, remove it withtools:node="remove":<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_CONTACTS" tools:node="remove" /> </manifest> -
List every place you touch contacts. For each one, decide: picker with a property, or full address book.
-
Replace invite and share flows with
pickContacts({ property }). -
Keep the permission only for core features, and prepare the declaration text: which feature, why the picker is not enough, a video if the feature is hard to find.
-
Ship a native build. Manifest and plugin changes need a new Play release. If you build in CI, Capgo Build can produce signed Android builds, and the Android keystore generator helps if you are setting up signing for the first time.
-
Ship UI changes over the air. Once the new native plugin is live, wording and flow tweaks around the picker can go out through Capgo live updates.
Migrating from another contacts plugin
If you use @capacitor-community/contacts (8.0.0 at the time of writing), its Android pickContact requests the contacts permission group before opening the picker, then reads the full contact from the address book. There is no property mode. The method names also differ (getContact vs getContactById, projection objects vs a fields array). Our comparison of the community contacts plugin and the Capgo plugin maps each call.
If you still use a Cordova contacts plugin inside Capacitor, now is a good time to replace it. See migrating from Cordova to Capacitor.
Troubleshooting
The picker returns a contact with no phone number. You called pickContacts() without property and the app has no READ_CONTACTS. Only id and displayName (and on Android 17+, picker-granted fields) come back. Add property.
Play Console still says the app declares READ_CONTACTS. A library adds it. Check the merged manifest and remove it with tools:node="remove". Also check older releases in other tracks, each active artifact counts.
Declaration rejected. Invites, referrals and sharing are listed as cases for the picker. Rework the feature or show clearly why the full list is core (for example friend matching that runs against the whole address book).
iOS asks for contacts permission. You call a method that reads the address book. Picking never needs it. Remove NSContactsUsageDescription if you only pick.
Key dates
| Date | What happens |
|---|---|
| April 15, 2026 | Policy announced |
| September 2026 | Play Console prompts apps declaring READ_CONTACTS to file a declaration |
| January 27, 2027 | Compliance mandatory for apps targeting Android 17 (API 37) or later |
Check the Play Console Help page for the policy before you submit, Google can update deadlines and wording.