If you've ever shipped a download feature in a Capacitor app, you know the failure modes: the user switches apps halfway through a 500 MB file, the operating system kills the process, or a big transfer quietly eats someone's mobile data plan. The official @capacitor/file-transfer plugin covers foreground transfers; we just released the Capacitor File Transfer plugin to handle everything around them.
Transfers as Tasks
The core idea: a transfer is a first-class, persisted object. Starting one returns an identifier immediately, the work runs in native code that outlives your web view (a background URLSession on iOS, a dataSync foreground service on Android), and the state can be queried later — even after an app restart.
Start a Download
import { FileTransfer } from '@capawesome-team/capacitor-file-transfer';
const startDownload = async () => {
const { id } = await FileTransfer.startDownload({
url: 'https://example.com/file.zip',
path: '/path/to/destination/file.zip',
headers: {
Authorization: 'Bearer ',
},
network: 'unmetered',
maxRetries: 3,
androidNotification: {
title: 'Downloading file',
text: 'The file is being downloaded.',
},
});
return id;
};
network: 'unmetered' keeps the download off mobile data, and maxRetries retries with backoff on network errors instead of failing on the first dropped packet.
Uploads, Including S3 Presigned URLs
Uploads send multipart/form-data by default and switch to a raw binary body for presigned URLs:
import { FileTransfer } from '@capawesome-team/capacitor-file-transfer';
const uploadToPresignedUrl = async () => {
const { id } = await FileTransfer.startUpload({
url: 'https://example.com/presigned-url',
path: '/path/to/source/file.jpg',
method: 'PUT',
uploadType: 'binary',
mimeType: 'image/jpeg',
});
return id;
};
The file streams straight from the file system into the request — no base64 detour, no blob in JavaScript memory.
Events That Survive Backgrounding
Transfers report their state through transferProgress, transferCompleted, and transferFailed events. The important detail: completed and failed events that occur while no listener is registered are retained and delivered as soon as a listener is added, so a download that finishes while your app is in the background still reaches your code on the next launch.
Pause, Resume, Restore
Downloads can be paused and resumed at any time — even after the process was killed, via resume data on iOS and HTTP Range requests on Android. And because transfers are persisted, getTransfers() rebuilds a download manager UI after a restart:
import { FileTransfer } from '@capawesome-team/capacitor-file-transfer';
const restoreTransferList = async () => {
const { transfers } = await FileTransfer.getTransfers();
return transfers.filter(transfer => transfer.state === 'running' || transfer.state === 'paused');
};
Uploads cannot be paused: plain HTTP uploads have no standard resume mechanism, so the plugin rejects instead of faking a pause that would restart from zero anyway.
What Happens in Each App State
Instead of leaving it to experimentation, the behavior is documented per platform:
| App state | Android | iOS |
|---|---|---|
| Foreground | Runs. | Runs. |
| Backgrounded | Runs in the dataSync foreground service. |
Runs in the background URLSession. |
| Killed by the OS | Interrupted; restored as failed, downloads resumable. |
Continued by the OS and delivered on relaunch. |
| Force-quit by the user | Interrupted; restored as failed, downloads resumable. |
Canceled by the OS (documented iOS behavior). |
Resuming an interrupted download requires the server to support the HTTP Range header.
Migrating from @capacitor/file-transfer
The switch is mostly a rename plus a change of model — transfers become asynchronous tasks:
@capacitor/file-transfer |
@capawesome-team/capacitor-file-transfer |
|---|---|
downloadFile({ url, path }) |
startDownload({ url, path }), resolves with { id }
|
uploadFile({ url, path }) |
startUpload({ url, path }), resolves with { id }
|
addListener('progress', ...) |
addListener('transferProgress', ...) |
| No equivalent |
transferCompleted and transferFailed events |
| No equivalent |
pauseTransferById, resumeTransferById, cancelTransferById
|
| No equivalent |
getTransferById, getTransfers
|
Availability
The plugin is part of the Capawesome Insiders subscription and requires Capacitor 8 or later. The full announcement is on our blog: Announcing the Capacitor File Transfer Plugin, and the plugin documentation covers the complete API.
Questions or feedback? Drop a comment — happy to answer.
This article was originally published by DEV Community and written by Robin.
Read original article on DEV Community