ionic-team / ionic-team/capacitor
[Bug]: capacitor/http Upload progress not supported
- Dominant language
- TypeScript
- Stars
- 16.7k
- Forks
- 1.3k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 10
Description
### Capacitor Version
I would like to address an issue that was automatically closed. I am having the same problem.
https://github.com/ionic-team/capacitor/issues/6658
Currently, the Capacitor HTTP plugin does not emit upload progress events, so passing onUploadProgress in requests has no effect. This is not a bug in user code — it is a limitation of the plugin:
• iOS: Uses URLSession.shared without a delegate. Upload progress requires implementing urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:).
• Android: Uses RequestBody directly without a wrapper. Upload progress requires a custom RequestBody that reports bytes written.
By contrast, the Capacitor File Transfer plugin handles native upload progress correctly on both platforms.
Upload progress is critical for mobile apps that handle large files. Adding native support would allow the Capacitor HTTP plugin to:
1. Support progress bars and UI feedback during uploads.
2. Reach feature parity with web APIs like XHR and Axios.
3. Make Capacitor a complete solution for file uploads without relying on additional plugins.
Request: Please add native upload progress support on iOS and Android, and expose it via notifyListeners("uploadProgress") so developers can track upload progress natively.
### Platforms Affected
- [x] iOS
- [x] Android
- [ ] Web
### Current Behavior
I hooked up the onprogress event in my code to show the progress of downloading a file to the user. But enabling the @capacitor/http plugin the progress doesn't show up anymore. I only see the popup and when the downloading is done, it will show the file. When I disable the plugin, the progress is shown again.
### Expected Behavior
The user should see the progress of the download.
### Project Reproduction
```javascript
makeXMLRequest(method, params, headers, useDatabase=true, cancelFunc, progressFunc) {
const req = this.checkParams(params, headers, useDatabase);
if (req.error !== undefined && req.error !== null) {
return Promise.reject(req.error);
}
return new Promise((resolve, reject) => {
let fetch = new XMLHttpRequest();
if (cancelFunc !== undefined && cancelFunc !== null) {
cancelFunc.cancel = () => {
fetch.abort(null);
};
}
// open a connection
fetch.open("POST", req.host);
// set extra request headers
for (const key in req.headers) {
fetch.setRequestHeader(key, req.headers[key]);
}
fetch.onload = (oEvent) => {
if (fetch.status >= 200 && fetch.status < 300) {
// difference between @capacitor/http and the 'normal' XMLHttpRequest
// @capacitor/http returns already a JSON
const result = JSON.parse(fetch.response);
resolve(result.result);
} else {
reject(fetch.statusText);
}
};
// send progress data back
fetch.onprogress = (oEvent) => {
if (progressFunc !== undefined && progressFunc !== null) {
progressFunc(oEvent.loaded, oEvent.total);
}
};
fetch.onerror = (oEvent) => {
reject(fetch.statusText);
};
// abort the connection
fetch.onabort = (oEvent) => {
reject(false);
};
// send the data
fetch.send(JSON.stringify(
{method: method, params: req.params, id: req.uid}
));
});
}
```
Contributor guide
Assessment
This issue has not been assessed yet.