La mejor alternativa a cordova-plugin-file-transfer para una app Capacitor depende de una pregunta: ¿la transferencia debe continuar si la app pasa a segundo plano? Para subidas y descargas normales con progreso, usa el @capacitor/file-transfer plugin oficial. Para archivos grandes, redes inestables o transferencias que deben seguir funcionando cuando el usuario cambia de app, usa @capgo/capacitor-uploader y @capgo/capacitor-downloader, que funcionan con las APIs nativas de transferencia en segundo plano. Esta guía las compara y mapea cada FileTransfer llamada.
¿cordova-plugin-file-transfer sigue deprecado?
La historia es confusa, así que aquí están los hechos:
- En 2017, el equipo de Apache Cordova anunció su intención de deprecar el plugin y dirigió a los desarrolladores a
XMLHttpRequestNivel 2. - El plugin nunca se marcó como obsoleto en npm. La versión 2.0.0 se lanzó en septiembre de 2023 y la 2.0.1 en agosto de 2026. El repositorio de GitHub está activo.
Así que no está muerto. Pero en una app de Capacitor sigue siendo un plugin de Cordova que:
- depende de
cordova-plugin-filey sucdvfile://yfile://modelo de rutas, - no encaja con Capacitor
Filesystemdirectorios y conversiones de URI, - no admite transferencias en segundo plano,
- usa APIs con estilo callback.
Si estás migrando desde Cordova, reemplazarlo con plugins nativos de Capacitor elimina una de las últimas razones para mantener cordova-plugin-file.
Por qué “usar solo fetch” falla con archivos grandes
La alternativa sugerida por Apache, XHR, funciona para JSON pequeños e imágenes. Falla en la transferencia real de archivos en una app híbrida:
- Memoria.
fetchy XHR te dan unBloboArrayBufferen el WebView. Un vídeo de 500 MB significa 500 MB en la memoria del WebView, más una copia para escribirlo a través del bridge. En dispositivos Android de gama media, eso hace que el renderer se crashee. - Suspensión. Cuando el usuario cambia de app, iOS suspende la WebView. Las peticiones en curso se atascan o fallan.
- Sin integración con el SO. Sin notificaciones de descarga del sistema, sin opción de solo Wi-Fi y sin reanudación tras una desconexión.
Los plugins nativos transmiten directamente entre el disco y la red y mantienen JavaScript fuera del flujo de datos.
Comparativa de opciones
| ¿Necesitas | cordova-plugin-file-transfer | @capacitor/file-transfer | @capgo/capacitor-uploader | @capgo/capacitor-downloader |
|---|---|---|---|---|
| Descarga a disco | Sí | Sí | No | Sí |
| Subir desde el disco | Sí | Sí | Sí | No |
| Eventos de progreso | Sí | Sí (progress listener) |
Sí (events) |
Sí (downloadProgress) |
| Datos de formulario multipart | Sí | Sí (predeterminado) | Sí | n/d |
| PUT de binario sin procesar (URLs prefirmadas) | Sí | Sí (configurar Content-Type) |
Sí (uploadType: 'binary') |
n/d |
| Varios archivos en una sola solicitud | No | No | Sí (files) |
n/d |
| Cancelar | abort() |
No | removeUpload |
stop |
| Pausar / reanudar | No | No | No | pause, resume |
| Reintentos | No | No | maxRetries |
No |
| Continúa en segundo plano | No | Mientras la app está activa | Sí | Sí |
| Solo Wi-Fi | No | No | No | network: 'wifi-only' |
| Detalles del error HTTP | http_status, body |
httpStatus, body |
Estado code en eventos | String de error en eventos |
| Implementación web | No | Sí | Sí (basada en fetch) | No |
La mayoría de las apps acaban usando @capacitor/file-transfer para transferencias pequeñas visibles para el usuario y los plugins Capgo para colas de upload de medios y descargas de contenido offline.
Opción 1: @capacitor/file-transfer
bun add @capacitor/file-transfer @capacitor/filesystem
bunx cap sync
El plugin requiere una ruta nativa completa. Consíguela en @capacitor/filesystem.
Descargar
import { FileTransfer } from '@capacitor/file-transfer';
import { Filesystem, Directory } from '@capacitor/filesystem';
export async function downloadReport(url: string) {
const { uri } = await Filesystem.getUri({
directory: Directory.Data,
path: 'reports/latest.pdf',
});
const progress = await FileTransfer.addListener('progress', (p) => {
if (p.type === 'download' && p.lengthComputable) {
console.log(`Downloaded ${Math.round((p.bytes / p.contentLength) * 100)}%`);
}
});
try {
const result = await FileTransfer.downloadFile({
url,
path: uri,
progress: true,
headers: { Authorization: 'Bearer <token>' },
});
return result.path;
} catch (error: any) {
if (error.code === 'OS-PLUG-FLTR-0010') {
console.error('HTTP error', error.data?.httpStatus, error.data?.body);
}
throw error;
} finally {
await progress.remove();
}
}
Subir
export async function uploadAvatar(path: string) {
const result = await FileTransfer.uploadFile({
url: 'https://api.example.com/v1/avatar',
path,
fileKey: 'avatar',
mimeType: 'image/jpeg',
headers: { Authorization: 'Bearer <token>' },
progress: true,
});
console.log(result.responseCode, result.response);
}
Las subidas son multipart por defecto. Para un stream raw, por ejemplo a una URL prefirmada de S3, establece method: 'PUT' y un explícito Content-Type header explícito como application/octet-stream.
Los códigos de error siguen el OS-PLUG-FLTR-XXXX formato. Los más útiles: 0007 archivo no encontrado, 0008 fallo de conexión, 0010 estado de error HTTP con httpStatus y body en error.data.
Opción 2: uploads en segundo plano con @capgo/capacitor-uploader
bun add @capgo/capacitor-uploader
bunx cap sync
import { Uploader } from '@capgo/capacitor-uploader';
await Uploader.addListener('events', async (event) => {
switch (event.name) {
case 'uploading':
console.log(event.id, `${event.payload.percent}%`);
break;
case 'completed':
console.log(event.id, 'done', event.payload.statusCode);
break;
case 'failed':
console.error(event.id, event.payload.error);
break;
}
// Completed and failed events are cached natively until acknowledged
if (event.eventId) {
await Uploader.acknowledgeEvent({ eventId: event.eventId });
}
});
const { id } = await Uploader.startUpload({
filePath: 'file:///path/to/video.mp4',
serverUrl: 'https://api.example.com/v1/videos',
method: 'POST',
uploadType: 'multipart',
fileField: 'video',
parameters: { albumId: '7' },
headers: { Authorization: 'Bearer <token>' },
maxRetries: 3,
notificationTitle: 'Uploading video', // Android only
});
Para URLs presignadas, usa method: 'PUT', que por defecto usa un body binario:
await Uploader.startUpload({
filePath,
serverUrl: presignedUrl,
method: 'PUT',
headers: { 'Content-Type': 'video/mp4' },
mimeType: 'video/mp4',
});
Varios archivos en una sola petición multipart:
await Uploader.startUpload({
serverUrl: 'https://api.example.com/v1/photos',
method: 'POST',
uploadType: 'multipart',
files: [
{ filePath: photo1, fieldName: 'images[]', mimeType: 'image/jpeg' },
{ filePath: photo2, fieldName: 'images[]', mimeType: 'image/jpeg' },
],
headers: { Authorization: 'Bearer <token>' },
});
Cancelar con Uploader.removeUpload({ id }).
¿Por qué confirmar los eventos? Los eventos completados y fallidos se guardan en una caché nativa persistente, por lo que se pueden volver a entregar si la app se cerró o pasó a segundo plano antes de procesarlos. Confirmar un evento lo elimina para que no se vuelva a emitir la próxima vez que se inicialice el plugin.
iOS setup. El uploader utiliza un proceso en segundo plano URLSession. Muchas apps no requieren configuración extra. Si los uploads deben continuar tras suspender la app, añade fetch a UIBackgroundModes. No lo añadas processing a menos que otra funcionalidad lo use BGTaskScheduler, ya que App Store Connect rechaza builds que lo declaren sin BGTaskSchedulerPermittedIdentifiers.
Opción 3: descargas en segundo plano con @capgo/capacitor-downloader
bun add @capgo/capacitor-downloader
bunx cap sync
import { CapacitorDownloader } from '@capgo/capacitor-downloader';
await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
console.log(id, `${progress}%`);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
console.log(id, 'completed');
});
await CapacitorDownloader.addListener('downloadFailed', ({ id, error }) => {
console.error(id, error);
});
await CapacitorDownloader.download({
id: 'course-42',
url: 'https://cdn.example.com/courses/42.zip',
destination: 'courses/42.zip',
headers: { Authorization: 'Bearer <token>' },
network: 'wifi-only',
priority: 'normal',
notification: 'progress', // Android: remove the system notification when done
});
// Later
await CapacitorDownloader.pause({ id: 'course-42' });
await CapacitorDownloader.resume({ id: 'course-42' });
const status = await CapacitorDownloader.checkStatus({ id: 'course-42' });
console.log(status.state, status.progress);
Una ruta relativa destination se resuelve en el directorio Documents en iOS y en el directorio de archivos externos de la app en Android. También puedes pasar una ruta absoluta o una file:// URL. Usa getFileInfo({ path }) para leer el tamaño y el MIME type tras la descarga, y stop({ id }) para cancelar y eliminar datos parciales.
Migrar desde cordova-plugin-file-transfer
Descargar
// Before
const ft = new FileTransfer();
ft.onprogress = (e) => e.lengthComputable && setProgress(e.loaded / e.total);
ft.download(
encodeURI(url),
cordova.file.dataDirectory + 'report.pdf',
(entry) => console.log(entry.toURL()),
(err) => console.error(err.code, err.http_status),
false,
{ headers: { Authorization: 'Bearer <token>' } },
);
// After
const { uri } = await Filesystem.getUri({ directory: Directory.Data, path: 'report.pdf' });
await FileTransfer.downloadFile({
url,
path: uri,
progress: true,
headers: { Authorization: 'Bearer <token>' },
});
Subir
// Before
const options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = 'photo.jpg';
options.mimeType = 'image/jpeg';
options.params = { albumId: '7' };
options.headers = { Authorization: 'Bearer <token>' };
new FileTransfer().upload(fileUrl, encodeURI(serverUrl), onSuccess, onError, options);
// After, foreground
await FileTransfer.uploadFile({
url: serverUrl,
path: fileUrl,
fileKey: 'file',
mimeType: 'image/jpeg',
params: { albumId: '7' },
headers: { Authorization: 'Bearer <token>' },
});
// After, background
await Uploader.startUpload({
filePath: fileUrl,
serverUrl,
uploadType: 'multipart',
fileField: 'file',
parameters: { albumId: '7' },
headers: { Authorization: 'Bearer <token>' },
});
Ten en cuenta que params en @capacitor/file-transfer son parámetros de consulta URL. Si tu servidor espera campos de formulario, usa el uploader de Capgo parametersque se envían como campos de formulario multipart.
Tabla de mapeo
| cordova-plugin-file-transfer | @capacitor/file-transfer | Plugins de Capgo |
|---|---|---|
download(source, target, ...) |
downloadFile({ url, path }) |
CapacitorDownloader.download({ id, url, destination }) |
upload(file, server, ...) |
uploadFile({ url, path }) |
Uploader.startUpload({ filePath, serverUrl }) |
onprogress |
addListener('progress') |
addListener('events') / addListener('downloadProgress') |
abort() |
no disponible | removeUpload({ id }) / stop({ id }) |
options.fileKey |
fileKey |
fileField o files[].fieldName |
options.params |
params (cadena de consulta) |
parameters (campos de formulario) |
options.httpMethod |
method |
method (POST o PUT) |
options.chunkedMode |
chunkedMode |
Gestionado de forma nativa |
cordova.file.dataDirectory |
Filesystem.getUri({ directory: Directory.Data }) |
Ruta relativa o file:// URL |
FileTransferError.http_status |
error.data.httpStatus |
event.payload.statusCode |
Rutas: lo que rompe las migraciones
Cordova code pasa cdvfile:// o cordova.file.* rutas por todas partes. En Capacitor:
- Obtén rutas nativas de
Filesystem.getUri()o del plugin que creó el archivo (cámara, selector de archivos, grabadora). - Para mostrar una imagen descargada en el WebView, convierte la ruta nativa con
Capacitor.convertFileSrc(path). - Capgo tiene un plugin de archivos y un file picker si
@capacitor/filesystemdoes not cover your case.
Solución de problemas
El upload funciona en Android pero falla en iOS con un error de archivo no encontrado. Pasaste una capacitor:// URL de WebView en lugar de una ruta file:// nativa. Usa la ruta de Filesystem.getUri().
El servidor rechaza el upload con un error 415. Discrepancia entre Multipart y raw body. Para URLs presigned usa PUT con uno explícito Content-Type.
Las subidas en segundo plano paran en iOS. El usuario cierra la app a la fuerza, cancelando sesiones en segundo plano, o fetch falta el modo background cuando lo necesitas.
Android muestra una notificación de descarga atascada. Configurar notification: 'progress' o 'hidden' en el descargador.
Crash por memoria en descargas grandes. Aún estás usando fetch o leyendo el archivo como base64 vía el bridge. Cambia a una descarga nativa a disco.
Lanza la migración
Cambiar plugins nativos requiere un nuevo build para la tienda, que Build de Capgo puede ejecutarse en la nube para iOS y Android. Después, el JavaScript que gestiona las subidas y descargas puede corregirse y mejorarse con actualizaciones Live Update de Capgo. Para el resto de la migración de Cordova a Capacitor, consulta nuestra guía de migración, y para otros SDK nativos de pago o legacy, el Alternativas a plugins de Ionic Enterprise.