La mejor alternativa a cordova-plugin-file-transfer para una app Capacitor depende de una pregunta: ¿la transferencia debe seguir funcionando si la app pasa a background? Para uploads y downloads normales con progreso, usa el @capacitor/file-transfer plugin oficial. Para archivos grandes, redes inestables o transferencias que deben continuar cuando el usuario cambia de app, usa @capgo/capacitor-uploader y @capgo/capacitor-downloader, que se ejecutan sobre APIs nativas de transferencia en segundo plano. Esta guía los compara y mapea cada FileTransfer llamada.
¿Sigue deprecado cordova-plugin-file-transfer?
La historia es confusa, así que aquí tienes los hechos:
- En 2017, el equipo de Apache Cordova anunció que planeaba deprecar el plugin y remitió a los desarrolladores a
XMLHttpRequestNivel 2. - El plugin nunca se marcó como deprecado 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 ruta, - no encaja con los de Capacitor,
Filesystemconversiones de URI y directorios, - has no background transfer support,
- usa APIs de estilo callback.
Si estás migrando desde Cordova, reemplazarlo con plugins nativos de Capacitor elimina una de las últimas razones para mantenerlo. cordova-plugin-file.
Por qué “solo usa fetch” falla con archivos grandes
El reemplazo sugerido por Apache, XHR, funciona para JSON pequeños e imágenes. Pero falla en transferencias de archivos reales en una app híbrida:
- Memoria.
fetchy XHR te dan unBloboArrayBufferen la WebView. Un video de 500 MB significa 500 MB en la memoria de la WebView, más una copia para escribirlo a través del bridge. En dispositivos Android de gama media, eso hace colapsar el renderer. - Suspensión. Cuando el usuario cambia de app, iOS suspende la WebView. Las peticiones en curso se bloquean o fallan.
- Sin integración con el SO. Sin notificación 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, evitando que JavaScript interfiera en el flujo de datos.
Opciones comparadas
| Necesidad | 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í (por defecto) | Sí | n/a |
| PUT binario puro (URLs presignadas) | Sí | Sí (configura Content-Type) |
Sí (uploadType: 'binary') |
n/a |
| Varios archivos en una sola petición | No | No | Sí (files) |
n/a |
| Cancelar | abort() |
No | removeUpload |
stop |
| Pausar / reanudar | No | No | No | pause, resume |
| Reintentos | No | No | maxRetries |
No |
| Sigue en segundo plano | No | Con la app 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 usan @capacitor/file-transfer para transferencias pequeñas visibles para el usuario y los plugins Capgo para colas de upload de media y descargas 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 uno 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 conexión fallida, 0010 estado de error HTTP con httpStatus y body en error.data.
Opción 2: subidas 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 cuerpo 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, para que puedan entregarse de nuevo si la app se cerró o pasó a segundo plano antes de procesarlos. Confirmarlos elimina el evento 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 las subidas 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 borrar datos parciales.
Migración 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 multipart form.
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 (query string) |
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 (camera, file picker, recorder). - Para mostrar una imagen descargada en la 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 file:// ruta nativa. Usa la ruta de Filesystem.getUri().
El servidor rechaza el upload con un 415. Discrepancia entre Multipart y raw body. Para URLs presigned usa PUT con un explícito Content-Type.
Las subidas en segundo plano fallan en iOS. El usuario cierra la app a la fuerza, cancelando las sesiones en segundo plano, o fetch falta el modo background cuando lo necesitas.
Android muestra una notificación de descarga bloqueada. Configurar notification: 'progress' o 'hidden' en el downloader.
Crasheos de memoria en descargas grandes. You are still using fetch o leyendo el archivo como base64 a través del bridge. Cambia a una descarga nativa a disco.
Lanza la migración
Cambiar plugins nativos requiere un nuevo build de la tienda, lo cual Build con Capgo puede ejecutarse en la nube para iOS y Android. Después, el JavaScript que gestiona los uploads y downloads puede corregirse y mejorarse con Live Updates 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, las alternativas de plugins de Ionic enterprise.