La gestione dei file in Capacitor si basa su alcuni elementi fondamentali: @capacitor/filesystem per leggere e scrivere file nelle directory dell'app, Capacitor.convertFileSrc per visualizzarli, un picker per importare i file dell'utente e plugin nativi per scaricare, caricare, aprire e condividere. Questa guida mostra ognuno di essi con Capacitor 8 code, spiega quale directory usare su iOS e Android e copre le insidie legate alla memoria e ai permessi che causano la maggior parte dei bug relativi ai file.
Il toolkit per la gestione file
| Attività | Plugin |
|---|---|
| Read, write, list, copy, delete in app storage | @capacitor/filesystem |
Mostra un file locale in <img>, <video>, <iframe> |
Capacitor.convertFileSrc (core) |
| Lascia all'utente la scelta di file o cartelle | @capgo/capacitor-file-picker |
| Download con avanzamento | @capacitor/file-transfer oppure @capgo/capacitor-downloader per download in background, in pausa |
| Upload con avanzamento | @capacitor/file-transfer oppure @capgo/capacitor-uploader per upload in background |
| Condividi un file o salvalo in Download | @capgo/capacitor-file-sharer oppure @capacitor/share |
| Apri un file in un'altra app | @capacitor-community/file-opener |
| Comprimi immagini | @capgo/capacitor-file-compressor |
| Comprimi ed estrai zip | @capgo/capacitor-zip |
Installa prima il plugin principale:
bun add @capacitor/filesystem
bunx cap sync
Scegli la directory giusta
Ogni chiamata Filesystem richiede un directory. Scegliere quello sbagliato è la causa più comune di file persi e app rifiutate.
Directory |
Posizione iOS | Posizione Android | Backup | Usa per |
|---|---|---|---|---|
Data |
Documents |
Dir file app (/data/data/<pkg>/files) |
iCloud iOS, Auto Backup Android | Dati utente di proprietà dell'app |
Documents |
Documents (stesso di Data) |
Pubblico Documents cartella |
Sì | Da evitare su Android 11+, vedi sotto |
Library |
Library |
Dir file app | iOS sì | File di supporto app, database |
LibraryNoCloud |
Library/NoCloud |
Dir file app | No | Contenuti grandi, ridownloadabili |
Cache |
Library/Caches |
Dir cache app | No | File temporanei, possono essere eliminati dal SO |
External |
Documents |
Directory file esterni dell'app | No | File di grandi dimensioni su Android, eliminati alla disinstallazione |
ExternalStorage |
Documents |
Radice dello storage condiviso | n/d | Solo legacy, non accessibile su Android 11+ |
Regole generali:
- Media di grandi dimensioni scaricabili (video offline, mappe) va in
LibraryNoCloud. Apple rifiuta le app che mettono contenuti di grandi dimensioni scaricabili di nuovo in cartelle con backup iCloud, come descritto nelle sue iOS Data Storage Guidelines. - File temporanei (esportazioni condivise una volta, miniature) vanno in
Cache. Non conservare mai l'unica copia dei dati utente lì. - I file che l'utente dovrebbe trovare in seguito fuori dalla tua app non appartengono affatto a una directory dell'app. Salvali tramite la share sheet, MediaStore Downloads o il file picker di sistema. Consulta la nostra guida su lo scoped storage di Android in Capacitor.
Scrivi un file
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,
});
Il encoding l'opzione decide come data viene interpretato. Con Encoding.UTF8, la stringa viene scritta come testo. Senza di essa, le piattaforme native decodificano data come base64 e scrivono byte grezzi. Dimenticarselo è il classico bug "il mio PDF è corrotto".
Per aggiungere a un file esistente, come un log, usa appendFile con le stesse opzioni.
Leggere un file
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,
});
Verificare se un file esiste con stat, che rifiuta se il file non è presente:
async function exists(path: string) {
try {
await Filesystem.stat({ path, directory: Directory.Data });
return true;
} catch {
return false;
}
}
Leggere file grandi in chunk
readFile restituisce l'intero file come un'unica stringa base64. Per un video da 200 MB significa centinaia di MB di stringhe in memoria JS. readFileInChunks lo passa invece in streaming:
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
},
);
Meglio ancora: evita del tutto di far passare i byte attraverso JS. La maggior parte dei task di "lettura di file pesanti" in realtà è "upload" o "visualizzazione", e per entrambi qui sotto ci sono percorsi nativi.
Elenca, copia, sposta ed elimina
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 e rename accettare toDirectory per spostarsi tra le directory, ad esempio da Cache a Data una volta completato il download. Accettano anche percorsi file:// assoluti quando si omette directory, ed è così che si importano i file restituiti da altri plugin.
Visualizza un file nella WebView
Non caricare mai immagini o video come data URL base64 se puoi evitarlo. Converti il percorso nativo in un URL servito dal server locale di Capacitor:
import { Capacitor } from '@capacitor/core';
const { uri } = await Filesystem.getUri({ path: 'images/logo.png', directory: Directory.Data });
img.src = Capacitor.convertFileSrc(uri);
Su iOS il risultato è simile a capacitor://localhost/_capacitor_file_/..., su Android https://localhost/_capacitor_file_/.... Funziona per <img>, <video>, <audio>, e fetch(), il che significa che puoi anche leggere un file come ArrayBuffer senza base64:
const buffer = await fetch(Capacitor.convertFileSrc(uri)).then((r) => r.arrayBuffer());
Salva i path, non gli URL convertiti. La forma convertita è valida solo per la sessione corrente dell'app.
Seleziona file dal dispositivo
Il picker di sistema dà il controllo all'utente e non richiede permessi di storage su Android 13+ o 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);
}
Ci sono anche pickImages, pickVideos, pickMedia e pickDirectory, e anche convertHeicToJpeg su iOS. Su iOS, types accetta solo i MIME type, non le estensioni.
I file selezionati potrebbero trovarsi in una posizione temporanea o dietro un content:// URI leggibile solo per un breve periodo. Se ti serve il file in seguito, copialo subito nella tua directory con Filesystem.copy.
Download dei file
Con progresso: @capacitor/file-transfer
Filesystem.downloadFile è deprecato in Filesystem 8. Il sostituto trasmette la risposta direttamente su disco in streaming:
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();
In background: @capgo/capacitor-downloader
Per i file di grandi dimensioni che devono continuare a scaricarsi quando l'utente esce dall'app e che possono essere messi in pausa e ripresi, usa il downloader Capgo, che si basa su DownloadManager su Android e in background URLSession su 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
});
A relative destination viene risolto nella directory file esterna dell'app su Android e nella Documents directory su iOS, gli stessi luoghi di Directory.External.
Carica file
Non leggere un file in base64 solo per inviarlo. Entrambe le opzioni di upload leggono nativamente dal disco:
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);
Per upload che devono sopravvivere al passaggio dell'app in background (video, upload batch di foto), usa @capgo/capacitor-uploader, che viene eseguito in background URLSession su iOS e un servizio di upload con notifica di avanzamento su 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',
});
Presigned PUT Gli URL verso l'object storage sono il pattern più affidabile per i file grandi: il tuo API firma l'URL e il dispositivo carica direttamente sullo storage.
Condividi o esporta un file
Per consentire all'utente di inviare un file a un'altra app o salvarlo in un luogo facilmente accessibile:
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',
});
Il file sharer di Capgo copia il file nella propria cartella cache e lo serve tramite un FileProvider, quindi non devi modificare file_paths.xml. Con quello ufficiale @capacitor/share, Android può condividere solo dalla cartella cache, a meno che tu non aggiunga altre cartelle a android/app/src/main/res/xml/file_paths.xml.
Apri un file in un'altra 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' });
Su iOS questo apre un'anteprima Quick Look. Su Android apre l'app predefinita per il MIME type o mostra un selettore. Se nessuna app può gestire il tipo, la chiamata fallisce: intercetta l'errore e offri la condivisione come alternativa.
Ricette comuni per i file
Genera un PDF: renderizza HTML con @capgo/capacitor-pdf-generator o crealo in JS con pdf-lib, quindi scrivi l'output base64 con writeFile. Il nostro tutorial: da scan a PDF mostra il flusso completo.
Rimuovi i GPS da una foto prima dell'upload: @capgo/capacitor-file-compressor rimuove i metadati EXIF su ogni piattaforma durante il ridimensionamento.
Ridimensiona o converti un'immagine: lo stesso compressore gestisce larghezza, altezza, qualità e formato di output, incluso HEIC in JPEG.
Bundle di più file: zip una cartella con @capgo/capacitor-zip e carica un unico archivio invece di centinaia di richieste.
Supporto web
@capacitor/filesystem funziona sul web usando IndexedDB, quindi il tuo code gira nel browser durante lo sviluppo. Differenze da tenere a mente: le letture binarie restituiscono un Blob, i file esistono solo nel profilo del browser e le quote di storage sono molto più basse. Per le build web, preferisci URL.createObjectURL(blob) per la visualizzazione.
Risoluzione problemi
“File does not exist”: hai mescolato directory, o passato un percorso assoluto insieme a un directory. Usa l'uno o l'altro.
File binari corrotti: hai scritto base64 con Encoding.UTF8. Rimuovi l'encoding.
L'app si blocca con file di grandi dimensioni: base64 attraverso il bridge. Usa chunk, file-transfer o fetch(convertFileSrc(uri)).
L'immagine non viene visualizzata su Android: hai usato un file:// URL direttamente. Convertilo con Capacitor.convertFileSrc.
Failed to find configured root quando condividi su Android: il file è al di fuori dei percorsi in file_paths.xml. Usa il file sharer di Capgo o copia in Cache prima.
I file spariscono su iOS: erano in Cache, che iOS elimina in caso di pressione sulla memoria.
Rifiuto da parte dell'App Store per lo storage iCloud: contenuti scaricabili di grandi dimensioni in Data oppure Documents. Spostalo in LibraryNoCloud.
Prossimi passi
Gran parte della logica dei file, ad esempio dove salvi i file, le regole di retry e la denominazione, risiede in JavaScript e può essere modificata con Live updates di Capgo dopo il rilascio. Per lo storage condiviso e i permessi su Android, prosegui con la nostra guida sullo scoped storage, e dai un'occhiata alla documentazione del file picker per ogni opzione del picker.