La política de permisos de contactos de Google Play implica que, desde el 27 de enero de 2027, las apps dirigidas a Android 17 (API nivel 37) o superior solo podrán solicitar READ_CONTACTS si el Android Contact Picker no puede cubrir una función principal, y deben justificarlo con una declaración en Play Console. Para la mayoría de las apps de Capacitor que leen contactos para invitar a un amigo, compartir un archivo o rellenar un número de teléfono, la solución es cambiar al Contact Picker y eliminar el permiso. Esta guía explica la política y muestra cómo hacerlo con @capgo/capacitor-contacts, y cubre qué hacer si realmente necesitas la agenda 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. Los puntos clave, según las páginas de ayuda de Play Console:
- Alcance: apps que apuntan a Android 17 (API 37) o superior y solicitan
READ_CONTACTS. - Regla: solo puedes solicitar
READ_CONTACTSsi el selector de contactos de Android no es suficiente para una función principal de tu app. - Declaración: Las apps que conserven el permiso deben completar una declaración en Play Console describiendo qué funciones lo necesitan y por qué el selector no es suficiente.
- Cronograma: Play Console comenzó a solicitar a las apps que declaren
READ_CONTACTSen septiembre de 2026. El cumplimiento es obligatorio el 27 de enero de 2027. Google menciona una extensión de autoservicio de 30 días en Play Console. - Exenciones: Las apps privadas y las apps de gestión de dispositivos empresariales quedan fuera de alcance.
- Datos: Los datos de contactos son personales y sensibles según la política de datos de usuario, y no puedes publicar datos de contactos no públicos sin la autorización de las personas a las que describen.
Casos de uso que pueden mantener READ_CONTACTS
Google enumera funciones principales como apps de gestión de contactos, apps de marcación y SMS, historial y filtrado de llamadas, CRM, funciones de accesibilidad, asistentes personales, coincidencia de amigos, copia de seguridad y restauración, y teclados o autocompletado.
Casos de uso que deberían cambiar al picker
Invitar o recomendar a alguien, compartir archivos, colaborar o elegir un contacto al que enviar dinero. Si esa es la razón por la que tu app lee contactos, la declaración no será aceptada. Usa el selector.
Qué cambia en Android 17
Android 17 incorpora un nuevo Contact Picker del sistema. Las aplicaciones obtienen una URI de sesión temporal y de solo lectura que expone únicamente los contactos y campos seleccionados por el usuario, sin necesidad de permisos. Para las apps que apuntan a Android 17, el sistema actualiza automáticamente el clásico Intent.ACTION_PICK contacts intent a este nuevo selector. Las apps también pueden solicitar campos de datos específicos (teléfono, correo electrónico, dirección postal) y selección múltiple mediante una nueva acción de intent.
En versiones anteriores de Android, seleccionar una fila de datos específica (por ejemplo, un número de teléfono) a través del selector de contactos siempre devolvía una URI que la app podía leer sin READ_CONTACTS. Es lo que hace posible un flujo sin permisos en todas las versiones de Android.
Por qué “elegir un contacto” requería READ_CONTACTS
Muchos plugins de Capacitor implementan «seleccionar contacto» abriendo el selector para un contacto completo y luego consultando la agenda por los números de teléfono y correos de ese contacto. 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, de modo que la URI de resultado 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. Cuando se establece, Android abre el selector contra la tabla de teléfonos, correos o direcciones postales y lee la fila seleccionada desde la URI concedida. Sin READ_CONTACTS es necesario en cualquier versión de Android. En iOS, el selector del sistema nunca necesita permiso 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 property picking, tu manifiesto combinado se mantendrá 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, pero solo añádelo si usas esos métodos.
Plan de migración para una app Capacitor
-
Audita el manifest fusionado. Compila 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 lo añade y no lo necesitas, elimínalo 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 todos los lugares donde accedas a contactos. Para cada uno, decide: un picker con una propiedad o la agenda completa.
-
Reemplaza los flujos de invitación y de compartir con
pickContacts({ property }). -
Mantén el permiso solo para funciones core, y prepara el texto de la declaración: qué función es, por qué el picker no basta y un video si cuesta encontrar la función.
-
Lanza un Native Build. Los cambios en el manifest y en los plugins requieren un nuevo release en Play. Si usas CI, Build de Capgo puede generar builds de Android firmados, y el Generador de keystore para Android te ayuda si configuras la firma por primera vez.
-
Lanza cambios de UI por OTA. Una vez que el nuevo plugin nativo esté activo, los ajustes de texto y flujo alrededor del picker pueden enviarse mediante actualizaciones Live Update de Capgo.
Migración desde otro plugin de contactos
Si usas @capacitor-community/contacts (8.0.0 al momento de escribir esto), 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 existe la propiedad mode. Los nombres de los métodos también difieren (getContact vs getContactById, objetos de proyección frente a un fields array). Nuestra comparación del 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. Consulta 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+, los campos concedidos por el picker) vuelven. 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 figuran como casos de uso del picker. Replantea la función o explica claramente por qué la lista completa es esencial (por ejemplo, matching de amigos que se ejecuta contra toda la agenda).
iOS pide 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 | Política anunciada |
| 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 que apuntan a Android 17 (API 37) o posterior |
Consulta la página de ayuda de Play Console sobre la política antes de enviar; Google puede actualizar plazos y redacción.