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.