---
title: An Alternative to cordova-plugin-file-transfer
description: Looking for a cordova-plugin-file-transfer alternative in Capacitor? Compare the XHR advice, @capacitor/file-transfer, and a background transfer API.
date:
  created: 2026-08-18
  updated: 2026-08-18
authors:
  - robingenz
categories:
  - Capacitor
  - Cordova
  - Guides
  - SDKs
links:
  - Capacitor File Transfer: sdks/capacitor/file-transfer.md
faq: true
---

# An Alternative to cordova-plugin-file-transfer

`cordova-plugin-file-transfer` is not dead. If you went looking for a cordova-plugin-file-transfer alternative because npm told you the plugin was deprecated, that flag came off years ago, and version 2.0.1 shipped on 10 August 2026. What has not moved is the API and the official advice that was supposed to replace it: "just use XHR", written in 2017, which pushes entire files through the WebView's JavaScript heap. This post maps every method and upload option onto the Capacitor side, compares the free official plugin with the paid Capawesome one, and says plainly which of the two you actually need.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

## Key Takeaways

- `cordova-plugin-file-transfer` carried an npm deprecation flag from version 0.3.0 through 1.7.0. Version 2.0.0 shipped on 13 September 2023, over five and a half years after the previous release, and 2.0.1 followed on 10 August 2026. The plugin is maintained again, but the API has not been modernized.
- Apache's official replacement advice, a [blog post from October 2017](https://cordova.apache.org/blog/2017/10/18/from-filetransfer-to-xhr2.html){:target="_blank"}, is XHR2 with `responseType = "blob"`. The plugin's own README concedes that for "large downloads, suffering from slow saving, timeouts, or crashes, this plugin is better suited for your use case".
- It is a Cordova-only plugin, supported on Android, iOS, and browser. Moving to Capacitor means picking a different plugin, not upgrading this one.
- `@capacitor/file-transfer` (official, free, currently 2.0.5) is the closest one-to-one swap: `downloadFile()`, `uploadFile()`, and an `addListener('progress')` callback. It documents no cancel, no pause and resume, and no background transfers.
- The [Capacitor File Transfer plugin](../../sdks/capacitor/file-transfer.md) from Capawesome is a paid Insiders plugin. It adds background continuation, pause and resume that survives process death, cancel, a persisted task store, automatic retries, and network constraints.

## Is cordova-plugin-file-transfer still deprecated?

No, not anymore, and this is the part most search results still get wrong. The npm deprecation flag sits on versions 0.3.0 through 1.7.0. Version 1.7.1 landed on 29 January 2018, then nothing happened for more than five years until 2.0.0 arrived on 13 September 2023. The most recent release, 2.0.1, is from 10 August 2026, and the [Apache repository](https://github.com/apache/cordova-plugin-file-transfer){:target="_blank"} is not archived.

So the plugin works, it installs without warnings, and someone is looking after it. That still leaves two problems. The README's own "Usage notice" points you at the Fetch API and XMLHttpRequest before it describes a single method, and the API itself is unchanged from the callback era: a `FileTransfer` object you construct per transfer, two success/error callbacks per call, and an assignable `onprogress` property. And it only targets `android`, `browser`, and `ios` under Cordova. There is no Capacitor build, so this is not a plugin you carry over during a migration.

## Why the official "just use XHR" advice breaks on large files

Because `responseType = "blob"` and `FormData` both route the entire file through the WebView's JavaScript heap. The pattern Apache recommends looks harmless enough for an icon or a PDF.

```js
var oReq = new XMLHttpRequest();
oReq.open('GET', 'https://example.com/video.mp4', true);
oReq.responseType = 'blob';
oReq.onload = function () {
  var blob = oReq.response;
  // Now write the blob to disk...
};
oReq.send();
```

With [`responseType` set to `"blob"`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType){:target="_blank"}, the response is only handed to you once the last byte has arrived, which means a 250 MB download becomes a 250 MB object in memory before anything reaches the file system. On Android that regularly ends in an `OutOfMemoryError`, and on iOS the WebView gets terminated under memory pressure. Uploads have the same shape in reverse: `FormData` reads the file into memory to build the request body. If you want the wider picture on why memory is the recurring theme in Capacitor file work, our [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md) covers the base64 and blob costs in detail.

There is a second problem that memory limits hide. An XHR lives in the WebView, so the transfer dies the moment the system suspends your app. The user switches to another app during a 500 MB download and comes back to a progress bar that reset itself.

## What should I use instead of cordova-plugin-file-transfer?

On Capacitor, use `@capacitor/file-transfer` for straightforward foreground downloads and uploads, and the [Capacitor File Transfer plugin](../../sdks/capacitor/file-transfer.md) from Capawesome when transfers need to keep running while the app is backgrounded, be paused and resumed, or be cancelled. The official plugin streams to disk instead of buffering in JavaScript, which already solves the memory problem the XHR advice creates. The Capawesome plugin goes further and moves the transfer out of the WebView entirely, into a background `URLSession` on iOS and a `dataSync` foreground service on Android.

One caveat before you pick: the Capawesome plugin requires a paid [Capawesome Insiders](../../insiders/index.md) licence, while both the Cordova plugin and the official Capacitor one are free. This is a genuine trade-off rather than an upgrade path, so it is worth being honest about which features you actually need.

## Comparing the cordova-plugin-file-transfer alternatives

Here is how the three options line up on the capabilities that usually decide the choice.

| Capability | XHR / fetch | `@capacitor/file-transfer` | Capacitor File Transfer |
| --- | --- | --- | --- |
| Background continuation | No | No | Yes (Android and iOS) |
| Pause and resume | No | No | Yes, downloads only |
| Cancel | Yes (`abort()`) | No | Yes |
| Progress events | Yes | Yes | Yes, throttled to ~100 ms |
| Automatic retries | No | No | Yes (`maxRetries`) |
| Wi-Fi-only constraint | No | No | Yes (`network: 'unmetered'`) |
| Persisted task list | No | No | Yes |
| Web support | Yes | Yes | No |
| Price | Free | Free | Paid (Insiders) |

Read the columns as three different jobs. XHR and fetch are fine for small payloads where the file comfortably fits in memory, `@capacitor/file-transfer` is the right default for anything that just needs to reach disk without blocking the UI, and the Capawesome plugin is for transfers whose failure the user would actually notice, such as offline video packs, course material, or media uploads over a shaky connection.

## Migrating from cordova-plugin-file-transfer

The shape of the change matters more than the individual renames. In Cordova, you construct a `FileTransfer` object per transfer and pass in the callbacks that will report the outcome. In the Capawesome plugin, you start a task, get an `id` back immediately, and observe the outcome through events. Here is how the methods and properties map across.

| `cordova-plugin-file-transfer` | Capacitor File Transfer |
| --- | --- |
| `new FileTransfer()` | No object. Each call returns a transfer `id` |
| `ft.download(source, target, win, fail)` | [`startDownload({ url, path })`](../../sdks/capacitor/file-transfer.md#startdownload) → `{ id }` |
| `ft.upload(fileURL, server, win, fail, options)` | [`startUpload({ url, path })`](../../sdks/capacitor/file-transfer.md#startupload) → `{ id }` |
| `ft.onprogress = (e) => …` (`loaded` / `total`) | [`addListener('transferProgress', …)`](../../sdks/capacitor/file-transfer.md#addlistenertransferprogress-) (`bytes` / `totalBytes` / `progress`) |
| `successCallback` | `transferCompleted` event (`path`, `responseCode`, `responseBody`) |
| `errorCallback` + `FileTransferError.code` 1–5 | `transferFailed` event (`errorCode`, `message`, `responseCode`) |
| `ft.abort()` | [`cancelTransferById({ id })`](../../sdks/capacitor/file-transfer.md#canceltransferbyid), which deletes partially transferred data |
| *No equivalent* | [`pauseTransferById`](../../sdks/capacitor/file-transfer.md#pausetransferbyid), `resumeTransferById`, `getTransferById`, [`getTransfers`](../../sdks/capacitor/file-transfer.md#gettransfers) |
| *No equivalent* | Background continuation, `maxRetries`, `network: 'unmetered'` |

The upload options are where a mechanical find-and-replace will bite you, because two of them changed their defaults rather than just their names.

| `FileUploadOptions` | Replacement |
| --- | --- |
| `fileKey` (default `file`) | `fileField` (default `file`) |
| `fileName` (default `image.jpg`) | Derived from `path`, no separate option |
| `httpMethod` (`POST` \| `PUT`) | `method` (`POST` \| `PUT`) |
| `mimeType` (default `image/jpeg`) | `mimeType`, with no default |
| `params` | `formFields` |
| `chunkedMode` (default `true`) | `uploadType: 'multipart' \| 'binary'` |
| `headers` | `headers` |
| `trustAllHosts` | No equivalent, by design |

### Downloads

Cordova's `download()` takes a `cdvfile://` URL as its target, or a full device path for backwards compatibility. The Capawesome plugin takes a plain device path, so this is usually the first thing you rewrite.

```js
var fileTransfer = new FileTransfer();

fileTransfer.download(
  encodeURI('https://example.com/file.zip'),
  'cdvfile://localhost/persistent/file.zip',
  function (entry) {
    console.log('Download complete: ' + entry.toURL());
  },
  function (error) {
    console.log('Download failed with code ' + error.code);
  }
);
```

The same download in the new API splits into two parts: a listener that receives the outcome, and the call that starts the task.

```typescript
import { FileTransfer } from '@capawesome-team/capacitor-file-transfer';

await FileTransfer.addListener('transferCompleted', event => {
  console.log('Download complete:', event.path);
});

const { id } = await FileTransfer.startDownload({
  url: 'https://example.com/file.zip',
  path: '/path/to/destination/file.zip',
});
```

Note what `await` means here. `startDownload(...)` resolves as soon as the task has been queued, not when the file exists, so any code that reads the file has to move into the `transferCompleted` listener.

### Uploads

Uploads carry two changes worth flagging. `params` becomes `formFields`, and `mimeType` loses its `image/jpeg` default, so an upload that relied on that default will now send no MIME type at all. That is a silent behavior change your backend may or may not forgive.

```js
var options = new FileUploadOptions();
options.fileKey = 'file';
options.mimeType = 'image/jpeg';
options.params = { albumId: '42' };

var fileTransfer = new FileTransfer();
fileTransfer.upload(fileURL, encodeURI('https://example.com/upload'), win, fail, options);
```

Everything moves into a single options object, and the file name is taken from `path` rather than set separately.

```typescript
const { id } = await FileTransfer.startUpload({
  url: 'https://example.com/upload',
  path: '/path/to/source/file.jpg',
  uploadType: 'multipart',
  fileField: 'file',
  mimeType: 'image/jpeg',
  formFields: { albumId: '42' },
});
```

There is no direct flag for `chunkedMode`. If your server expects a raw request body rather than a multipart form, for example an S3 presigned URL, use `uploadType: 'binary'` with `method: 'PUT'`.

### Progress, cancel, and error handling

Progress moves from an assignable `onprogress` property to a regular listener, and the per-object `abort()` becomes an id-based cancel. Three listeners cover the states a transfer can reach.

```typescript
await FileTransfer.addListener('transferProgress', event => {
  console.log(`${event.bytes}/${event.totalBytes}`, event.progress);
});
await FileTransfer.addListener('transferCompleted', event => {
  console.log('Completed:', event.id, event.responseCode);
});
await FileTransfer.addListener('transferFailed', event => {
  console.error('Failed:', event.errorCode, event.message);
});
```

One detail solves a bug that was hard to avoid in the Cordova version. Completed and failed events that occur while no listener is registered are retained and delivered as soon as a listener is added. In Cordova, a page navigation that tore down the callback closure meant the result was simply lost, and the transfer looked like it had hung forever.

## FAQ

### Is the Capacitor File Transfer plugin free?

No. It is an Insiders plugin, installed from the Capawesome npm registry with a licence key. See the [Installation](../../sdks/capacitor/file-transfer.md/#installation) section for the setup and the [Capawesome Insiders](../../insiders/index.md) page for what a licence includes. If you need a free option, `@capacitor/file-transfer` is the one to use.

### Does it work on the web?

No. `startDownload(...)` and `startUpload(...)` reject as unavailable on the web, because the whole point of the plugin is a native background API that a browser cannot offer. The remaining methods are implemented and simply report that no transfer exists.

### Can I pause an upload?

Not in this version. Plain HTTP uploads have no standard resume mechanism, so `pauseTransferById(...)` rejects with the `TRANSFER_NOT_PAUSABLE` error code instead of pretending to suspend the request. Downloads can be paused and resumed at any time, including after process death, as long as the server honors the HTTP `Range` header.

### What happens to a transfer when the user force-quits the app?

It depends on the platform, and the plugin documents both cases rather than papering over them. On Android, the transfer is interrupted and restored as `failed`, and downloads can be resumed. On iOS, the system cancels background transfers when the user force-quits, which is documented OS behavior and cannot be worked around.

### Do I have to migrate everything at once?

No, migrate per call site. The Cordova plugin and the Capacitor plugins never coexist in the same project anyway, so the migration happens as part of the wider Cordova-to-Capacitor move, and each transfer can be rewritten independently.

## Need Help Migrating Off Cordova?

Moving a real app off Cordova touches more than file transfers, and the plugins with no direct Capacitor equivalent are usually what stalls the project. We do this migration work with teams regularly and can walk through your plugin list with you.

[Book a Free Consultation](https://cal.com/team/capawesome/ionic-appflow-migration){ .md-button .md-button--primary }

## Conclusion

The awkward part of replacing `cordova-plugin-file-transfer` is that there is no single successor. There is a free plugin that matches the old API almost line for line, and a paid one built around a different model where transfers are tasks that outlive the WebView. The size of your files and whether users are allowed to leave the app mid-transfer decide which of the two you need, and for a lot of apps the free one is genuinely enough.

If you are working through a broader Cordova migration, our post on the [Cordova Hot Code Push Alternative for OTA Updates](./cordova-hot-code-push-alternative.md) covers another plugin with no drop-in successor, and [Exploring the Capacitor File Compressor API](./exploring-the-capacitor-file-compressor-api.md) is worth a read if you are shrinking files before uploading them.

Questions about a specific migration case? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"} and ask, or subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} to hear about new plugins and updates.
