La política de permisos de contactos de Google Play implica que, desde el 27 de enero de 2027, las apps orientadas a Android 17 (API nivel 37) o superior solo podrán solicitar READ_CONTACTS si el Android Contact Picker no puede soportar una función principal, lo cual debe justificarse con una declaración en Play Console. Para la mayoría de las apps Capacitor que leen contactos para invitar amigos, compartir archivos o rellenar un número, la solución es usar el Contact Picker y eliminar el permiso. Esta guía explica la política y cómo hacerlo con @capgo/capacitor-contacts, y explica qué hacer si realmente necesitas la agenda de contactos completa.
Qué dice la política
Google anunció la política el 15 de abril de 2026 como parte de una serie de actualizaciones de privacidad. Puntos clave según la ayuda de Play Console:
- Alcance: apps que apunten a Android 17 (API 37) o superior y soliciten
READ_CONTACTS. - Regla: solo puedes solicitar
READ_CONTACTSsi el Android Contact Picker no es suficiente para una funcionalidad principal de tu app. - Declaración: las apps que mantengan el permiso deben completar una declaración en Play Console describiendo qué funciones lo requieren y por qué el picker no es suficiente.
- Cronograma: Play Console empezó a mostrar avisos a las apps que declaran
READ_CONTACTSen septiembre de 2026. El cumplimiento es obligatorio el 27 de enero de 2027. Google menciona una extensión de 30 días en Play Console. - Exenciones: las apps privadas y de gestión de dispositivos empresariales quedan fuera del alcance.
- Datos: los datos de contactos son personales y sensibles según la política de User Data; no puedes publicar datos de contactos no públicos sin autorización de las personas implicadas.
Casos de uso que pueden mantener READ_CONTACTS
Google enumera funciones principales como apps de gestión de contactos, marcadores y SMS, historial y filtrado de llamadas, CRM, accesibilidad, asistentes personales, matching de amigos, backup y restore, y teclados o autocomplete.
Casos de uso que deben cambiar al picker
Invitar o referir a alguien, compartir archivos, colaborar o elegir un contacto para enviar dinero. Si esa es la razón por la que tu app lee contactos, la declaración será rechazada. Usa el picker.
Qué cambia en Android 17
Android 17 incluye un nuevo Contact Picker del sistema. Las apps reciben un URI de sesión temporal de solo lectura que solo expone los contactos y campos seleccionados por el usuario, sin necesidad de permisos. Para apps que apunten a Android 17, el sistema actualiza automáticamente el clásico Intent.ACTION_PICK contacts intent a este nuevo picker. Las apps también pueden solicitar campos de datos específicos (teléfono, email, dirección postal) y selección múltiple mediante una nueva acción de intent.
En versiones antiguas de Android, seleccionar una fila de datos específica (por ejemplo, un número de teléfono) mediante el selector de contactos siempre devolvía una URI que la app podía leer sin READ_CONTACTS. Eso es lo que permite un flujo sin permisos en cualquier versión de Android.
Por qué “pick a contact” solía requerir READ_CONTACTS
Muchos plugins de Capacitor implementan “pick contact” abriendo el selector para un contacto completo y luego consultando la agenda para obtener sus números y emails. Esa segunda consulta requiere READ_CONTACTS. Así, incluso las apps que solo querían un número de teléfono terminaban declarando el permiso, que es exactamente lo que la política busca evitar.
La solución es pedir al selector el campo específico que necesitas, para que la URI resultante ya contenga los datos.
Selecciona teléfono, email o dirección sin READ_CONTACTS
@capgo/capacitor-contacts añadió una property opción en la versión 8.1.0. Al activarla, Android abre el selector sobre la tabla de teléfonos, emails o direcciones postales y lee la fila seleccionada desde la URI concedida. No se requiere READ_CONTACTS en ninguna versión de Android. En iOS, el selector del sistema nunca necesita permisos de contactos.
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;
}
Con property, el resultado contiene el contacto id, displayName y solo la propiedad seleccionada. Se ignora la selección múltiple; la selección de propiedades siempre devuelve un único valor. Si el usuario cancela, verifica si el array está vacío.
El plugin no declara READ_CONTACTS o WRITE_CONTACTS en su propio manifiesto, así que si solo usas la selección de propiedades, tu manifiesto combinado se mantiene limpio.
Cuando aún necesites READ_CONTACTS
Estos métodos del plugin leen la agenda de contactos y siguen requiriendo el permiso:
pickContacts()opickContact()sinproperty, cuando quieras el registro de contacto completogetContacts(),getContactById(),countContacts()getGroups(),getGroupById(),getAccounts()
Si tu app es un CRM, una herramienta de backup o una función de búsqueda de amigos, es un uso principal legítimo. Añade el permiso tú mismo y presenta la declaración:
<!-- 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);
}
La política se enfoca en READ_CONTACTS. Escribir contactos con WRITE_CONTACTS (para createContact, updateContactById, deleteContactById) no es el objeto de esta política; añádela solo si usas esos métodos.
Plan de migración para una app Capacitor
-
Audita el manifest fusionado. Genera tu release y abre
android/app/build/intermediates/merged_manifest/(o usa la pestaña Merged Manifest de Android Studio). Algunos plugins añadenREAD_CONTACTSsin que te des cuenta. Si un plugin la añade y no la necesitas, elimínala contools: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> -
Lista cada lugar donde accedas a contactos. Para cada uno, decide: un picker con una propiedad o la agenda completa.
-
Reemplaza los flujos de invitación y compartir con
pickContacts({ property }). -
Mantén el permiso solo para funciones core, y prepara el texto de la declaración: qué funcionalidad es, por qué el picker no es suficiente y un video si la función es difícil de encontrar.
-
Lanza un Native Build. Los cambios en el Manifest y en los plugins requieren una nueva versión en Play. Si usas CI, Capgo Build puedes generar builds de Android firmados, y el generador de Android keystore te ayuda si estás configurando la firma por primera vez.
-
Lanza cambios de UI vía OTA. Una vez que el nuevo plugin nativo esté activo, los ajustes de texto y flujo del picker pueden enviarse mediante Live Updates de Capgo.
Migración desde otro plugin de contactos
Si usas @capacitor-community/contacts (8.0.0 al momento de escribir), su Android pickContact solicita el grupo de permisos de contactos antes de abrir el selector y luego lee el contacto completo de la agenda. No tiene modo de propiedad. Los nombres de los métodos también varían (getContact frente a getContactById, objetos de proyección frente a un fields array). Nuestra comparativa entre el plugin de contactos de la comunidad y el plugin Capgo mapea cada llamada.
Si aún usas un plugin de contactos de Cordova dentro de Capacitor, ahora es un buen momento para reemplazarlo. Mira migrar de Cordova a Capacitor.
Solución de problemas
El selector devuelve un contacto sin número de teléfono. Llamaste a pickContacts() sin property y la app no tiene READ_CONTACTS. Solo id y displayName (y en Android 17+, campos permitidos por el picker) se recuperan. Añade property.
Play Console sigue diciendo que la app declara READ_CONTACTS. Una librería lo añade. Revisa el manifest fusionado y elimínalo con tools:node="remove". También revisa versiones antiguas en otros tracks; cada artefacto activo cuenta.
Declaración rechazada. Invitaciones, referidos y compartir se listan como casos para el picker. Rediseña la función o explica claramente por qué la lista completa es esencial (por ejemplo, un matching de amigos que consulta toda la agenda).
iOS solicita permiso de contactos. Llamas a un método que lee la agenda. El picker nunca lo necesita. Elimina NSContactsUsageDescription si solo eliges.
Fechas clave
| Fecha | Qué sucede |
|---|---|
| 15 de abril de 2026 | Anuncio de la política |
| Septiembre de 2026 | Play Console solicita a las apps que declaren READ_CONTACTS presentar una declaración |
| 27 de enero de 2027 | Cumplimiento obligatorio para apps con Android 17 (API 37) o superior |
Consulta la ayuda de Play Console sobre la política antes de enviar; Google puede cambiar plazos y términos.