Si necesitas una alternativa a los contactos de la comunidad de Capacitor, la sustitución más completa es @capgo/capacitor-contacts. Cubre lo que @capacitor-community/contacts permite (leer, crear, eliminar y seleccionar contactos) y añade la capacidad de actualizar un contacto sin cambiar su ID, paginación, grupos, cuentas, pantallas nativas de contactos y selección sin permisos en Android, algo clave para la política de contactos de Google Play de 2027. Esta guía compara ambos y mapea cada llamada para que puedas migrar en una tarde.
Por qué buscar una alternativa
@capacitor-community/contacts se mantiene y tiene una versión 8 de Capacitor. Es una buena opción para flujos sencillos de lectura y creación. Los equipos suelen quedarse cortos con él por estas razones:
- Sin método de actualización. Puedes crear y eliminar contactos, pero no editarlos. Eliminar y volver a crear le asigna al contacto un nuevo ID, lo que rompe cualquier referencia que hayas guardado y puede provocar la pérdida de datos que el plugin no lee.
- Sin paginación.
getContactsdevuelve toda la agenda en una sola llamada. En un dispositivo con 5.000 contactos y fotos, eso es lento y consume mucha memoria. - La selección requiere el permiso en Android. Android
pickContactsolicita el permiso de contactos antes de abrir el selector y luego lee el contacto de la libreta de direcciones. Bajo política de permisos de contactos de Google Play, las apps orientadas a Android 17 solo pueden mantenerREAD_CONTACTSpara funciones clave. - Sin grupos, cuentas ni pantallas nativas. No permite listar grupos, elegir la cuenta donde se guarda un nuevo contacto ni abrir el formulario de "nuevo contacto" del sistema.
Comparativa de funciones
| Característica | @capacitor-community/contacts 8.0 | @capgo/capacitor-contacts 8.1 |
|---|---|---|
| Leer uno / todos los contactos | getContact, getContacts |
getContactById, getContacts |
| Selección de campos | projection objeto |
fields array |
| Paginación | No | limit, offset |
| Contar contactos | No | countContacts |
| Crear | createContact |
createContact |
| Actualizar in situ | No | updateContactById |
| Eliminar | deleteContact |
deleteContactById |
| Seleccionar un contacto | pickContact |
pickContact |
| Seleccionar varios | No | pickContacts({ multiple: true }) |
| Seleccionar un teléfono/email/dirección sin permiso | No | pickContacts({ property }) |
| Grupos | No | getGroups, getGroupById, createGroup, deleteGroupById |
| Cuentas | No | getAccounts |
| Pantallas nativas de creación / vista / edición | No | displayCreateContact, displayContactById, displayUpdateContactById |
| Estados de permisos de lectura y escritura separados | No (alias único) | readContacts, writeContacts |
| Estado de acceso limitado en iOS | limited |
limited |
| Abrir ajustes | No | openSettings |
Instalar
bun add @capgo/capacitor-contacts
bunx cap sync
El plugin no añade permisos a tu manifiesto de Android. Añade solo lo que uses:
<!-- Needed for getContacts, getContactById, countContacts, groups, accounts -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- Needed for createContact, updateContactById, deleteContactById, groups writes -->
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
En iOS, añade NSContactsUsageDescription a Info.plist si lees o escribes en la agenda de contactos. Para seleccionar no hace falta.
Guía de migración, llamada por llamada
Permisos
// Before
import { Contacts } from '@capacitor-community/contacts';
const perm = await Contacts.requestPermissions();
if (perm.contacts !== 'granted') return;
// After
import { CapacitorContacts } from '@capgo/capacitor-contacts';
const status = await CapacitorContacts.requestPermissions({ permissions: ['readContacts'] });
if (status.readContacts !== 'granted' && status.readContacts !== 'limited') {
await CapacitorContacts.openSettings();
return;
}
En iOS 18 y versiones posteriores, los usuarios pueden conceder acceso a un subconjunto de contactos. El plugin lo reporta como limited. Dalo por concedido y espera solo los contactos compartidos.
Leer todos los contactos
// Before: whole address book, projection object
const { contacts } = await Contacts.getContacts({
projection: { name: true, phones: true, emails: true },
});
const first = contacts[0];
console.log(first.name?.display, first.phones?.[0]?.number);
// After: fields array, paginated
const pageSize = 200;
let offset = 0;
for (;;) {
const { contacts } = await CapacitorContacts.getContacts({
fields: ['displayName', 'givenName', 'familyName', 'phoneNumbers', 'emailAddresses'],
limit: pageSize,
offset,
});
render(contacts);
if (contacts.length < pageSize) break;
offset += pageSize;
}
declare function render(list: unknown[]): void;
Usa countContacts() primero si quieres mostrar una barra de progreso.
Leer un contacto
// Before
const { contact } = await Contacts.getContact({
contactId: id,
projection: { name: true, phones: true },
});
// After
const { contact } = await CapacitorContacts.getContactById({
id,
fields: ['displayName', 'phoneNumbers'],
});
if (!contact) console.log('Not found');
Crear un contacto
// Before
const { contactId } = await Contacts.createContact({
contact: {
name: { given: 'Ada', family: 'Lovelace' },
phones: [{ type: PhoneType.Mobile, number: '+44 20 7946 0000' }],
emails: [{ type: EmailType.Work, address: 'ada@example.com' }],
},
});
// After
const { id } = await CapacitorContacts.createContact({
contact: {
givenName: 'Ada',
familyName: 'Lovelace',
phoneNumbers: [{ type: 'MOBILE', value: '+44 20 7946 0000' }],
emailAddresses: [{ type: 'WORK', value: 'ada@example.com' }],
},
});
displayName es de solo lectura en el plugin Capgo. Escribe givenName y familyName, y el dispositivo formatea el nombre visible.
Actualizar un contacto (nuevo)
await CapacitorContacts.updateContactById({
id,
contact: {
givenName: 'Ada',
familyName: 'King',
jobTitle: 'Analyst',
phoneNumbers: [{ type: 'MOBILE', value: '+44 20 7946 0000' }],
},
});
El ID se mantiene, así que cualquier referencia guardada seguirá funcionando. Lee el contacto primero y devuelve los campos que quieras conservar para no borrar nada por accidente.
Si prefieres que el usuario lo edite, abre el editor del sistema:
await CapacitorContacts.displayUpdateContactById({ id });
Eliminar un contacto
// Before
await Contacts.deleteContact({ contactId: id });
// After
await CapacitorContacts.deleteContactById({ id });
Seleccionar un contacto
// Before: requests the contacts permission on Android first
const { contact } = await Contacts.pickContact({
projection: { name: true, phones: true },
});
// After: one phone number, no permission on Android or iOS
import { ContactProperty } from '@capgo/capacitor-contacts';
const { contacts } = await CapacitorContacts.pickContacts({
property: ContactProperty.PhoneNumber,
});
const phone = contacts[0]?.phoneNumbers?.[0]?.value;
Si necesitas el registro completo del contacto desde el picker, llama a pickContact() sin property. En Android, eso devuelve el contacto completo solo cuando READ_CONTACTS se concede.
Mapeo de campos
| Payload de la comunidad | Capgo Contact |
|---|---|
contactId |
id |
name.display |
displayName (solo lectura) |
name.given / middle / family |
givenName / middleName / familyName |
name.prefix / suffix |
namePrefix / nameSuffix |
organization.company |
organizationName |
organization.jobTitle |
jobTitle |
birthday { day, month, year } |
birthday { day, month, year } |
note |
note |
phones[].number |
phoneNumbers[].value |
emails[].address |
emailAddresses[].value |
urls[] (strings) |
urlAddresses[].value |
postalAddresses[].region / postcode |
postalAddresses[].state / postalCode |
image.base64String |
photo (string base64) |
Los valores de Type también cambian: el plugin Capgo usa strings en mayúsculas como 'MOBILE', 'HOME', 'WORK' para teléfonos y correos electrónicos.
Migra una pantalla a la vez
Los dos plugins se registran con nombres nativos diferentes (Contacts y CapacitorContacts), para que puedan instalarse juntos mientras migras las llamadas. Un pequeño adaptador mantiene el resto de tu app sin cambios:
import { CapacitorContacts } from '@capgo/capacitor-contacts';
export interface AppContact {
id: string;
name: string;
phones: string[];
}
export async function listContacts(limit = 200, offset = 0): Promise<AppContact[]> {
const { contacts } = await CapacitorContacts.getContacts({
fields: ['displayName', 'phoneNumbers'],
limit,
offset,
});
return contacts.map((c) => ({
id: c.id ?? '',
name: c.displayName ?? '',
phones: (c.phoneNumbers ?? []).map((p) => p.value),
}));
}
Cuando hayas migrado la última llamada, elimina el plugin de la comunidad y ejecuta bunx cap sync, y revisa el Android manifest combinado para confirmar que solo queden los permisos necesarios.
Solución de problemas
getContacts devuelve una lista vacía en iOS. El usuario otorgó acceso limitado y no compartió contactos, o denegó el acceso. Revisa readContacts y ofrece openSettings().
El contacto creado no aparece en una cuenta específica en Android. Pasa un account (desde getAccounts()) en el contacto que crees.
Las fotos ralentizan las listas. No solicites photo en vistas de lista. Carga con getContactById cuando el usuario abre un contacto.
Avisos de Play Console READ_CONTACTS. Mover los flujos de invitación y compartir a pickContacts({ property }) y eliminar el permiso si nada más lo necesita.
Lánzalo
Cambiar plugins es un cambio nativo, por lo que requiere un build para la tienda. Build de Capgo puede generar builds de iOS y Android en la nube. Después, las correcciones a tus pantallas de contactos son JavaScript y pueden publicarse con actualizaciones Live Update de Capgo. El API completo está en la documentación del plugin de contactos.