Saltar al contenido principal

Guía de manejo de archivos de Capacitor: leer, escribir, seleccionar y compartir

Guía de manejo de archivos de Capacitor: leer y escribir archivos, elegir directorios, seleccionar, mostrar, abrir, compartir, descargar y subir archivos en iOS y Android.

Leer, escribir y compartir archivos en una app móvil de Capacitor

El manejo de archivos en Capacitor se basa en unos pocos pilares: @capacitor/filesystem para leer y escribir archivos en los directorios de la app, Capacitor.convertFileSrc para mostrarlos, un selector para importar archivos de usuario y plugins nativos para descargar, subir, abrir y compartir. Esta guía muestra cada uno con Capacitor 8 code, explica qué directorio usar en iOS y Android, y cubre los problemas de memoria y permisos que causan la mayoría de los errores de archivos.

El toolkit de gestión de archivos

Tarea Plugin
Leer, escribir, listar, copiar y borrar en storage de la app @capacitor/filesystem
Mostrar un archivo local en <img>, <video>, <iframe> Capacitor.convertFileSrc (core)
Permitir al usuario elegir archivos o una carpeta @capgo/capacitor-file-picker
Descarga con progreso @capacitor/file-transfer o @capgo/capacitor-downloader para descargas en segundo plano y pausables
Subida con progreso @capacitor/file-transfer o @capgo/capacitor-uploader para uploads en segundo plano
Comparte un archivo o guárdalo en Descargas @capgo/capacitor-file-sharer o @capacitor/share
Abre un archivo en otra app @capacitor-community/file-opener
Comprime imágenes @capgo/capacitor-file-compressor
Comprimir y descomprimir @capgo/capacitor-zip

Primero instala el plugin core:

bun add @capacitor/filesystem
bunx cap sync

Elige el directorio correcto

Cada llamada al Filesystem requiere un directory. Elegir el incorrecto es la causa más común de archivos perdidos y apps rechazadas.

Directory Ubicación en iOS Ubicación en Android Respaldado Se usa para
Data Documents Directorio de archivos de la app (/data/data/<pkg>/files) iCloud en iOS, Auto Backup en Android Datos de usuario propiedad de la app
Documents Documents (igual que Data) Público Documents carpeta Sí Evitar en Android 11+, ver abajo
Library Library Directorio de archivos de la app iOS sí Archivos de soporte, bases de datos
LibraryNoCloud Library/NoCloud Directorio de archivos de la app No Contenido pesado que se puede re-descargar
Cache Library/Caches Directorio de caché de la app No Archivos temporales, el SO puede eliminarlos
External Documents Directorio de archivos externos de la app No Archivos grandes de la app en Android, se borran al desinstalar
ExternalStorage Documents Raíz del almacenamiento compartido n/d Solo legacy, no accesible en Android 11+

Reglas generales:

  • Medios grandes descargables (vídeos offline, tiles de mapas) van en LibraryNoCloud. Apple rechaza las apps que guardan contenido grande descargable en carpetas respaldadas por iCloud, según sus iOS Data Storage Guidelines.
  • Archivos temporales (los exports que compartes una vez, las miniaturas) van en Cache. Nunca guardes ahí la única copia de los datos del usuario.
  • Archivos que el usuario debería encontrar después fuera de tu app no pertenecen a un directorio de app en absoluto. Guárdalos mediante la hoja de compartir, MediaStore Downloads o el selector de archivos del sistema. Consulta nuestra guía de almacenamiento con alcance de Android en Capacitor.

Escribir un archivo

import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';

// Text file
await Filesystem.writeFile({
  path: 'notes/today.txt',
  data: 'Hello from Capacitor',
  directory: Directory.Data,
  encoding: Encoding.UTF8,
  recursive: true, // create notes/ if missing
});

// JSON
await Filesystem.writeFile({
  path: 'settings.json',
  data: JSON.stringify({ theme: 'dark' }),
  directory: Directory.Data,
  encoding: Encoding.UTF8,
});

// Binary file: pass base64 and no encoding
await Filesystem.writeFile({
  path: 'images/logo.png',
  data: base64Png,
  directory: Directory.Data,
  recursive: true,
});

El encoding la opción decide cómo data se interpreta. Con Encoding.UTF8, el string se escribe como texto. Sin ella, las plataformas nativas decodifican data como base64 y escriben bytes sin procesar. Olvidar esto es el clásico bug de “mi PDF está corrupto”.

Para añadir contenido a un archivo existente, como un log, usa appendFile con las mismas opciones.

Leer un archivo

const { data } = await Filesystem.readFile({
  path: 'settings.json',
  directory: Directory.Data,
  encoding: Encoding.UTF8,
});
const settings = JSON.parse(data as string);

// Binary: returns base64 on native, a Blob on web
const image = await Filesystem.readFile({
  path: 'images/logo.png',
  directory: Directory.Data,
});

Comprueba si un archivo existe con stat, que rechaza cuando el archivo no existe:

async function exists(path: string) {
  try {
    await Filesystem.stat({ path, directory: Directory.Data });
    return true;
  } catch {
    return false;
  }
}

Leer archivos grandes en chunks

readFile devuelve el archivo completo como una cadena base64. Para un video de 200 MB, eso son cientos de MB de strings en la memoria de JS. readFileInChunks usa un stream en su lugar:

await Filesystem.readFileInChunks(
  { path: 'videos/clip.mp4', directory: Directory.Data, chunkSize: 1024 * 1024 },
  (chunk, err) => {
    if (err) return console.error(err);
    if (chunk === null) return console.log('done');
    processChunk(chunk.data); // base64 chunk
  },
);

Mejor aún, evita pasar los bytes por JS. La mayoría de las tareas de "leer un archivo grande" son en realidad "subirlo" o "mostrarlo", y ambas tienen rutas nativas más abajo.

Listar, copiar, mover y eliminar

const { files } = await Filesystem.readdir({ path: 'notes', directory: Directory.Data });
for (const f of files) console.log(f.name, f.type, f.size, f.mtime);

await Filesystem.mkdir({ path: 'archive', directory: Directory.Data, recursive: true });

await Filesystem.copy({
  from: 'notes/today.txt',
  to: 'archive/today.txt',
  directory: Directory.Data,
});

await Filesystem.rename({
  from: 'archive/today.txt',
  to: 'archive/2026-10-01.txt',
  directory: Directory.Data,
});

await Filesystem.deleteFile({ path: 'notes/today.txt', directory: Directory.Data });
await Filesystem.rmdir({ path: 'notes', directory: Directory.Data, recursive: true });

copy y rename aceptan toDirectory para moverte entre directorios, por ejemplo desde Cache a Data después de que termine una descarga. También aceptan rutas file:// absolutas cuando omites directory, que es como importas archivos devueltos por otros plugins.

Mostrar un archivo en el WebView

Evita cargar imágenes o videos como URLs de datos base64 si puedes evitarlo. Convierte la ruta nativa en una URL servida por el servidor local de Capacitor:

import { Capacitor } from '@capacitor/core';

const { uri } = await Filesystem.getUri({ path: 'images/logo.png', directory: Directory.Data });
img.src = Capacitor.convertFileSrc(uri);

En iOS, el resultado se ve así: capacitor://localhost/_capacitor_file_/..., en Android: https://localhost/_capacitor_file_/.... Funciona para <img>, <video>, <audio>, y fetch(), lo que significa que también puedes leer un archivo como ArrayBuffer sin base64:

const buffer = await fetch(Capacitor.convertFileSrc(uri)).then((r) => r.arrayBuffer());

Guarda rutas, no URLs convertidas. El formato convertido solo es válido para la sesión actual de la app.

Seleccionar archivos del dispositivo

El selector del sistema da control al usuario y no requiere permiso de almacenamiento en Android 13+ ni iOS:

bun add @capgo/capacitor-file-picker
bunx cap sync
import { CapgoFilePicker } from '@capgo/capacitor-file-picker';

const { files } = await CapgoFilePicker.pickFiles({
  types: ['application/pdf', 'image/*'],
  limit: 5,
  readData: false, // keep false for anything bigger than a few MB
});

for (const file of files) {
  console.log(file.name, file.mimeType, file.size, file.path);
}

También hay pickImages, pickVideos, pickMedia y pickDirectory, además convertHeicToJpeg en iOS. En iOS, types solo acepta tipos MIME, no extensiones.

Los archivos seleccionados pueden estar en una ubicación temporal o detrás de un content:// URI que solo es legible durante un tiempo. Si necesitas el archivo después, cópialo en tu propio directorio de inmediato con Filesystem.copy.

Descargar archivos

Con progreso: @capacitor/file-transfer

Filesystem.downloadFile está obsoleto en Filesystem 8. El reemplazo transmite la respuesta directamente al disco:

bun add @capacitor/file-transfer
bunx cap sync
import { FileTransfer } from '@capacitor/file-transfer';
import { Directory, Filesystem } from '@capacitor/filesystem';

const { uri } = await Filesystem.getUri({ path: 'downloads/report.pdf', directory: Directory.Data });

const listener = await FileTransfer.addListener('progress', (p) => {
  if (p.lengthComputable) setProgress(p.bytes / p.contentLength);
});

await FileTransfer.downloadFile({
  url: 'https://example.com/report.pdf',
  path: uri,
  progress: true,
  headers: { Authorization: `Bearer ${token}` },
});

await listener.remove();

En segundo plano: @capgo/capacitor-downloader

Para archivos grandes que deben seguir descargándose cuando el usuario sale de la app, y que se pueden pausar y reanudar, usa el downloader de Capgo, que se basa en DownloadManager en Android y en segundo plano URLSession en iOS:

import { CapacitorDownloader } from '@capgo/capacitor-downloader';

await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
  console.log(id, progress);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
  console.log('done', id);
});

await CapacitorDownloader.download({
  id: 'course-video-12',
  url: 'https://cdn.example.com/course/12.mp4',
  destination: 'videos/12.mp4',
  network: 'wifi-only',
  notification: 'progress', // Android
});

Una ruta relativa destination se resuelve en el directorio de archivos externos de la app en Android y el Documents directorio en iOS, los mismos lugares que Directory.External.

Subir archivos

No conviertas un archivo a base64 solo para enviarlo. Ambas opciones de subida leen del disco de forma nativa:

import { FileTransfer } from '@capacitor/file-transfer';

const result = await FileTransfer.uploadFile({
  url: 'https://api.example.com/upload',
  path: uri,          // full file:// path
  fileKey: 'file',
  mimeType: 'application/pdf',
  headers: { Authorization: `Bearer ${token}` },
  progress: true,
});
console.log(result.responseCode, result.bytesSent);

Para las subidas que deben continuar si la app pasa a segundo plano (videos, subida masiva de fotos), usa @capgo/capacitor-uploader, que se ejecuta en segundo plano URLSession en iOS y un servicio de subida con notificación de progreso en Android:

import { Uploader } from '@capgo/capacitor-uploader';

const { id } = await Uploader.startUpload({
  filePath: uri,
  serverUrl: 'https://api.example.com/upload',
  method: 'PUT', // binary body, ideal for presigned S3 or R2 URLs
  mimeType: 'video/mp4',
});

Prefirmadas PUT Las URLs de almacenamiento de objetos son el patrón más fiable para archivos grandes: tu API firma la URL y el dispositivo sube directamente al almacenamiento.

Compartir o exportar un archivo

Para que el usuario pueda enviar un archivo a otra app o guardarlo donde pueda encontrarlo:

import { FileSharer } from '@capgo/capacitor-file-sharer';

await FileSharer.share({
  filename: 'report.pdf',
  path: uri,
  contentType: 'application/pdf',
});

// Android: write to Downloads via MediaStore. iOS: opens the share sheet ("Save to Files")
await FileSharer.save({
  filename: 'report.pdf',
  path: uri,
  contentType: 'application/pdf',
});

El file sharer de Capgo copia el archivo en su propia carpeta de caché y lo sirve mediante un FileProvider, así que no necesitas editar file_paths.xml. Con el plugin oficial @capacitor/share, Android solo puede compartir desde la carpeta de caché a menos que añadas otras carpetas a android/app/src/main/res/xml/file_paths.xml.

Abre un archivo en otra app

bun add @capacitor-community/file-opener
bunx cap sync
import { FileOpener } from '@capacitor-community/file-opener';

await FileOpener.open({ filePath: uri, contentType: 'application/pdf' });

En iOS, esto abre una vista previa de Quick Look. En Android, abre la app predeterminada para el tipo MIME o muestra un selector. Si ninguna app puede manejar el tipo, la llamada falla, así que captúrala y ofrece compartir en su lugar.

Recetas comunes para archivos

Generar un PDF: renderiza HTML con @capgo/capacitor-pdf-generator o constrúyelo en JS con pdf-lib, luego escribe la salida en base64 con writeFile. Nuestro tutorial para escanear a PDF muestra el flujo completo.

Eliminar GPS de una foto antes de subirla: @capgo/capacitor-file-compressor elimina los metadatos EXIF en todas las plataformas al redimensionar.

Redimensionar o convertir una imagen: el mismo compresor maneja ancho, alto, calidad y formato de salida, incluyendo HEIC a JPEG.

Agrupar muchos archivos: comprime una carpeta con @capgo/capacitor-zip y sube un solo archivo en lugar de cientos de peticiones.

Soporte web

@capacitor/filesystem funciona en la web usando IndexedDB, por lo que tu code se ejecuta en el navegador durante el desarrollo. Diferencias a tener en cuenta: las lecturas binarias devuelven un Blob, los archivos solo existen en el perfil del navegador y las cuotas de almacenamiento son mucho menores. Para builds web, prefiere URL.createObjectURL(blob) para mostrarlos.

Solución de problemas

«El archivo no existe»: mezclaste directorios, o pasaste una ruta absoluta junto con un directory. Usa uno u otro.

Archivos binarios corruptos: escribiste base64 con Encoding.UTF8. Elimina la codificación.

La app se cierra con archivos grandes: base64 a través del bridge. Usa chunks, file-transfer o fetch(convertFileSrc(uri)).

La imagen no se muestra en Android: usaste un file:// URL directamente. Conviértela con Capacitor.convertFileSrc.

Failed to find configured root al compartir en Android: el archivo está fuera de las rutas en file_paths.xml. Usa el compartidor de archivos de Capgo o copia a Cache primero.

Los archivos desaparecen en iOS: estaban en Cache, que iOS limpia por falta de espacio.

Rechazo de App Store por almacenamiento iCloud: contenido descargable pesado en Data o Documents. Muévelo a LibraryNoCloud.

Próximos pasos

La mayor parte de la lógica de archivos, como el almacenamiento, las reglas de reintento y el nombrado, reside en JavaScript y puede ajustarse con actualizaciones Live Update de Capgo después del lanzamiento. Para la parte de Android del almacenamiento compartido y los permisos, continúa con nuestra guía de scoped storage, y revisa la documentación del file picker para cada opción del picker.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.