I forget where, but a user on one of my posts recently asked about handling multiple uploads with Cordova, so I thought I'd whip up a quick demo. As always, the code below is all on GitHub (link will be at the bottom), so you can skip my post completely and just grab the bits if you want.
First off, the only reason this is even a little bit complex is because the upload method of the File Transfer plugin is asynchronous. Luckily there is an easy (heh, ok, kinda easy) way to handle multiple asynchronous responses - Promises. If you are using Ionic, then I'd strongly suggest using ngCordova. It includes a "Promise-fied" version of the File Transfer plugin already. But I didn't want to assume Angular so I decided to skip ngCordova and instead simply use the promise support from jQuery. (Reminder, I've got 2.5 hours of jQuery training, including promises, here: https://www.youtube.com/playlist?list=PL_z-rqJYNijrtVAc5qQbkzHnDELANGiOn)
For my demo, I simply used the Camera plugin to let you select multiple images from the device. Each image is added to the DOM. Here is that code:
var images = [];
var $imagesDiv;
document.addEventListener("deviceready", init, false);
function init() {
$("#addPicture").on("touchend", selPic);
$imagesDiv = $("#images");
$("#uploadPictures").on("touchend", uploadPics);
}
function selPic() {
navigator.camera.getPicture(function(f) {
var newHtml = "<img src='"+f+"'>";
$imagesDiv.append(newHtml);
images.push(f);
if(images.length === 1) {
$("#uploadPictures").removeAttr("disabled");
}
}, function(e) {
alert("Error, check console.");
console.dir(e);
}, {
quality: 50,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.FILE_URI
});
}
I assume this stuff is rather simple, but if not, just let me know in the comments below. Here is a screen shot of the app in action.

As you can guess, the upload button at the bottom there will begin the process. For my testing, I set up a ColdFusion script to simply save the uploads to a temporary directory. To add a bit of randonmness though, it will randomly reject images by outputting 0. On successful uploads, it will output 1.
Here is the remainder of the code:
function uploadPics() {
console.log("Ok, going to upload "+images.length+" images.");
var defs = [];
images.forEach(function(i) {
console.log('processing '+i);
var def = $.Deferred();
function win(r) {
console.log("thing done");
if($.trim(r.response) === "0") {
console.log("this one failed");
def.resolve(0);
} else {
console.log("this one passed");
def.resolve(1);
}
}
function fail(error) {
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
def.resolve(0);
}
var uri = encodeURI("http://localhost/testingzone/test.cfm");
var options = new FileUploadOptions();
options.fileKey="file";
options.fileName=i.substr(i.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
var ft = new FileTransfer();
ft.upload(i, uri, win, fail, options);
defs.push(def.promise());
});
$.when.apply($, defs).then(function() {
console.log("all things done");
console.dir(arguments);
});
}
So from top to bottom, what I'm doing is creating an array of promise objects. Or more specifically, the jQuery version of them. I then run an upload call for each image selected by the user. I check the result from the server and either resolve the promise with 0 or 1 based on what the server said.
Finally, I've got a call to $.when to handle waiting for all these asynch processes to finish. I don't actually show anything, I just console.dir, but you could imagine checking the results and doing - well - whatever makes sense.
I hope this is useful for folks, and as always, let me know if you have any questions. You can find the complete source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiupload
Archived Comments
"If you are using Ionic, then I’d strongly suggest using ngCordova."
ngCordova and its "Promise-fied" File Transfer plugin can be used even if you're not using Ionic. All it takes is AngularJS as the "engine" of your mobile app. I used it recently with PhoneGap / plain Cordova.
Yep, I was thinking that but typed too quickly. :)
Hi Raymond, Thank you for this post. It's really useful. Please I have a question: how can I integrate the progressEvent.
Actually I a kinda implement it but it only working when the upload start with the first image but the rest of images are not uploading. I should attached with every promise but i didn't found a solution. Please any tips?
There is no real way to do a 'meta' progressEvent for this. In theory, you could check the file sizes of the pics, and then in the progressEvent, figure out how much of one picture you've done, which in theory lets you know the percent total over all. Doable - just feels like a lot of work. I'd maybe just report as each image completes.
Thank you for the reply. Actually it works now for every Image. What I did, is that before uploading a pic I set a name for every image. Also, I make some editing because of the specification of my task. After That I used a switch case to make the progression for every image.
It's working as I want. For the record I am using angularJS
here my solution:
$scope.uploadPics = function (sell_hash) {
console.log("Ok, going to upload " + $scope.images.length + " images.");
var defs = [];
var sell_hash = sell_hash;
console.log(sell_hash);
$scope.upload_percentage_main = 0;
$scope.upload_percentage_main_show = false;
$scope.upload_percentage_detail_1 = 0;
$scope.upload_percentage_detail_1_show = false;
$scope.upload_percentage_detail_2 = 0;
$scope.upload_percentage_detail_2_show = false;
$scope.upload_percentage_detail_3 = 0;
$scope.upload_percentage_detail_3_show = false;
$scope.upload_percentage_detail_4 = 0;
$scope.upload_percentage_detail_4_show = false;
$scope.images.forEach(function (fileURI, count) {
console.log('processing ' + fileURI);
console.log(count);
var def = $.Deferred();
function win(r) {
console.log("thing done");
if ($.trim(r.response) === "0") {
console.log("this one failed");
def.resolve(0);
} else {
console.log("this one passed");
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
def.resolve(1);
}
}
function fail(error) {
console.log("An error has occurred: Code = " + error.code);
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
def.resolve(0);
}
var uri = encodeURI("http://www.xxxxx.info/hhhh/...");
// var filename = fileURI.substr(i.lastIndexOf('/') + 1);
if (count === 0) {
var filename = 'main.jpg';
var directory_path = '/html/beta4/www/data/sell/' + sell_hash + '/main';
} else {
//var filename = fileURI.substr(fileURI.lastIndexOf('/') + 1);
var filename = 'detail_' + count + '.jpg';
var directory_path = '/html/beta4/www/data/sell/' + sell_hash;
}
var imageName = filename;
console.log(directory_path);
var options = new FileUploadOptions();
options.fileKey = "file";
options.fileName = filename;
options.mimeType = "image/jpeg";
options.params = {'directory': directory_path, 'fileName': filename};
var ft = new FileTransfer();
ft.upload(fileURI, uri, win, fail, options);
defs.push(def.promise());
ft.onprogress = function (progressEvent) {
if (progressEvent.lengthComputable) {
console.log(progressEvent.loaded);
console.log(progressEvent.total);
switch (imageName) {
case 'main.jpg':
$scope.upload_percentage_main_show = true;
$scope.upload_percentage_main = Math.floor(progressEvent.loaded / progressEvent.total * 100);
$scope.$apply();
console.log($scope.upload_percentage_main);
console.log("here file name main");
break;
case 'detail_1.jpg':
$scope.upload_percentage_detail_1_show = true;
$scope.upload_percentage_detail_1 = Math.floor(progressEvent.loaded / progressEvent.total * 100);
$scope.$apply();
console.log($scope.upload_percentage_detail_1);
console.log("here file name detail_1");
break;
case 'detail_2.jpg':
$scope.upload_percentage_detail_2_show = true;
$scope.upload_percentage_detail_2 = Math.floor(progressEvent.loaded / progressEvent.total * 100);
$scope.$apply();
console.log($scope.upload_percentage_detail_2);
console.log("here file name detail_2");
break;
case 'detail_3.jpg':
$scope.upload_percentage_detail_3_show = true;
$scope.upload_percentage_detail_3 = Math.floor(progressEvent.loaded / progressEvent.total * 100);
$scope.$apply();
console.log($scope.upload_percentage_detail_3);
console.log("here file name detail_3");
break;
case 'detail_4.jpg':
$scope.upload_percentage_detail_4_show = true;
$scope.upload_percentage_detail_4 = Math.floor(progressEvent.loaded / progressEvent.total * 100);
$scope.$apply();
console.log($scope.upload_percentage_detail_4);
console.log("here file name detail_4");
break;
default:
alert('Perdu !');
}
}
};
});
$.when.apply($, defs).then(function () {
console.log("all things done");
// Cleans up the image files that were taken by the camera, that were stored in a temporary storage location.
navigator.camera.cleanup();
$scope.loading = false;
$scope.$apply();
// We should run the expression "$location.path("/sells");" as function in the $apply() method, to take effect
$rootScope.$apply(function () {
$location.path("/sells");
});
console.dir(arguments);
});
};
Hi Raymond, I want to ask, I followed your post, but when I choose more than 1 image, it only upload the latest one, even when I check the processed file on console log, it processed each image.
Can you help me?
I'd check on the server to ensure it isn't an issue there. That's all I can think of right now.
I have a need to upload multiple images in one request using Cordova.
However the solution on the page sends the images in separate requests.
Is there a way to achieve multiple files upload in single request?
How can the file transfer plugin be modified to upload multiple files.
I can't speak to modifying the File Transfer plugin. You would need to look at the source code. As for how I'd do it in JavaScript, the XHR2 specification allows for file uploads in a POST operation. You could use that to send multiple items in one POST.
Could you provide an implementation of uploading multiple images using XHR2 and mutli image picker plugin?
without using <input type="file" multiple="">
I have tried modifying the File Transfer Plugin but it hasnt worked.
Yes - I will have a demo on this blog in about two hours. I won't post back here, so just check the blog in - well - 2 to 3 hours.
Kindly implement using cordova.
Do provide the link?
It won't be in Cordova, but will be in code you can easily use in Cordova. I don't have a link as it isn't published yet. Just come back to the blog and look for the latest entry. :)
Okay I can implement it in cordova if the implementation is done only with image URI. As this plugin https://github.com/wymsee/c...
returns image URI of the selected images.
My objective is to select multiple images from gallery and upload in one request.
Well I'd suggest reading my blog post. It is a generic solution that you should be able to apply. For a *specific* solution to fit your needs, if you want me to write that it would have to be a paid engagement.
I have gone through your blog post however it uploads images in separate requests. I just require some guidance to achieve my objective. I would appreciate any help from your side. Thank you.
I don't think you understand me. The blog post isn't done yet. I'm writing a *new* blog post. Like right now. Check the blog (go to www.raymondcamden.com) in about an hour and see the *new* post.
I do get you. I do appreciate for taking out your time for this. Will definitely check out your blog within an hour.
I have this need too,have you resolved it?
I did - see my later posts. :) (Right hand side of blog has latest posts.)
Dear Raymond, thank you for this and your other cordova tutorials - and the github repository of them all. I have a quick question about implementing this file transfer example. I've used a php script (tested in a browser) and it keeps returning Undefined index: fileToUpload. It seems to not recognise the incoming file.
I'm pretty sure this is an issue with the sever / php not your cordova example and I wondered if you had come across anything like it? (I realise you are using cold fusion).
Thanks!
Does PHP have a way to dump the entire scope of form fields? If so, I'd use that to see what is being sent to it.
Hi Raymond, thanks it does (var_dump($_POST)) and that helped a lot.
I now have this working in iOS8 and Android lollipop (on actual devices and a remote server with a php script).
I added some params to your uploadPics() which get passed to the php script.
uploadPics() snippet:
var options = new FileUploadOptions();
options.fileKey="file";
options.fileName=i.substr(i.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
//added
var params = new Object();
params.directory = "uploads"; //must correspond with $_POST['directory']
params.fileName = i.substr(i.lastIndexOf('/')+1); //must correspond with $_POST['fileName']
options.params = params;
//end added
var ft = new FileTransfer();
Then the php script (very basic and for testing only as contains no security checks):
This post
http://www.gajotres.net/usi...
contained all of the above.
Hope this is useful to someone.
The php script in its entirety
$location = $_POST['directory'];
$uploadfile = $_POST['fileName'];
$uploadfilename = $_FILES['file']['tmp_name'];
if(move_uploaded_file($uploadfilename, $location.'/'.$uploadfile)){
echo 'File successfully uploaded!';
} else {
echo 'Upload error!';
}
Your examples is great! any other example with progress bar?
Nope, sorry. Technically possible of course.
Thanks for tutorial, I found some simple & useful article for uploading images to php server using phonegap file transfer plugin http://phonegappro.com/tuto...
Hi thank you for this.. is there any way to restrict the size of the file uploaded?
Yes. You would need to use the File plugin to get the size. You could then add restrictions on a per file basis (no image over X) and/or a total.
great thanks. and i was also wondering do you know the best way to allow the user to crop an image after they select it? in my app i want a user to select a photo for their profile pic and i want the profile pic to be of dimensions approx 200X200px before it is uploaded. thanks
The Camera plugin allows for simple editing, but I'm not sure what that will allow for *existing* pictures.
great thanks for the information :)
Hi Raymond. I was wondering can you help (again lol).. I had the file transfer code in your tutorial working successfully in a phonegap build with adobe phonegap. using the following in my config file: <gap:plugin name="org.apache.cordova.file-transfer" spec="0.5.0" source="pgb"/> but now in my phonegap account they are telling me the following "This app uses plugins from the PhoneGap Build repository. These plugins won't be accessible after Nov 15th, 2016" so ive researched this a bit and ive found out that you should change to the npm version of the plugin. so i tried this in my config: <plugin name="cordova-plugin-file-transfer" source="npm" version="1.3.0"/> however its not working.... any ideas? thanks
How is it not working?
well it doesnt seem to be executing the code anymore as the file transfers are not coming back as passed or failed...
Do you see anything when you do a remote debug?
Hi Raymond,
Firstly, thank you for putting up the code, it works really well at uploading multiple images. However, I am having a few problems getting the following part of the code to fire :
$.when.apply($, defs).then(function() { });
I can upload many images successfully and get the responses back to my terminal telling me it has completed. It looks like the Deferred objects aren't being resolved.
It would be great if you could look over my code and perhaps give me a hint of where I have gone wrong. I think my understanding of Deferred and Promises is lacking. I don't understand the purpose of the arguments being passed in the def.resolve(1) or def.resolve(0) calls.
My function takes objects from a array of objects holding references to .png files which were captured using an html canvas. The $.when.apply($, defs).then(function() { }); does fire if there are no objects in the array or an error is returned.
function syncSignatures() {
var defs = [];
var i;
$(".loader").show();
for(i = 0; i < signatureCache.signatures.length; i++){
var def = $.Deferred();
var image;
function win(r) {
console.log("thing done : ", r);
if($.trim(r.response) === "0") {
console.log("this one failed");
def.resolve(0);
} else {
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
var file = r.response;
file = file.replace(" uploaded successfully", "");
for(s = 0; s < signatureCache.signatures.length; s++){
var sig = signatureCache.signatures[s];
if(sig.fileName == file){
signatureCache.signatures.splice(s, 1);
break;
}
}
def.resolve(1);
}
}
function fail(error) {
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
console.log("upload http status " + error.http_status);
console.log("upload error body " + error.body);
def.resolve(0);
}
image = signatureCache.signatures[i];
var options = new FileUploadOptions();
console.log(image.signature);
options.fileKey = "file";
options.fileName = image.fileName;
options.mimeType = "image/jpeg";
options.chunkedMode = true;
var params = {};
params.jobId = image.jobid;
params.deviceUUID = image.deviceUUID;
params.name = image.name;
params.timestamp = image.timestamp;
options.params = params;
var ft = new FileTransfer();
ft.upload(image.signature, encodeURI(baseUrl+'syncImages'), win, fail, options);
defs.push(def.promise());
}
$.when.apply($, defs).then(function() {
console.log("all signatures uploaded");
console.dir(arguments);
$(".loader").hide();
});
}
Once again, thank you for the original code, as with other topics you discuss on your blog it is incredibly helpful and clearly written and so helpful to someone like me embarking on a career as a software developer.
Well first off - does the *original* code work for you? As for what resolve(1) and resolve(0) were doing: They are basically saying, "This particular Promise is done and either had a good or bad result." I could have used reject() as well to return an error, but decided on using a status result (1 or 0) instead.
Also - please post your code someplace else, like Pastebin, as it is too hard to read here.
Also - be sure to test with remote debugging and report on what you see in the console.
Hi Raymond. I was wondering can you help again? :) I've been using the file transfer plugin in my project and works grand. thanks :) As I am packaging up the app with phonegap build, I have been sending the images to a php page on a remote server and all working grand. I used the following header in the php page header("Access-Control-Allow-Origin: *"); Now I have decided to use ssl on my server so now I am trying to send the images to a uri with https:// e.g var uri = encodeURI(https://wwwexample.com/uplo..."); However the images are failing and I am getting the following error: "http_status":406, "body":"<head>Not Acceptable!</head><body>An appropriate representation of the requested resource could not be found on this server. This error was generated by Mod_Security</body>".. I have looked up error 406 and it seems to be to do with the headers. I was just wondering would you know how to fix it? thanks
Hmm - maybe try trustAllHosts option defined here: https://cordova.apache.org/...
Ok thank you :)
In my case duplicate images are uploaded even i choose different images in android.but its working in simulator.Any issue related to this?
I'd need to know more, debug, etc.
I removed correctOrientation: true issue was fixed
How do i go on about to upload video type files too?
What did you try? This post demonstrates how to do it with images, but the source doesn't really matter. You should be able to apply this to any file.
I tried it with the original source code and I've added the cordova plugins (file-transfer & camera) and using my own php file but sadly I'm not able to add any other source other than image (jpeg).
I'm new to ionic so my knowledge isn't that vast. I switched out the mimetype to video/mp4 and it doesn't work on either android or ios via ionic view. Could you point me in the right direction as to what I'm doing wrong.
Thanks a bunch and I know you get this kind of questions alot.
Not sure what I can tell you here - the changes you made were correct, but you also have to change *where* the files are sourced from. You need to figure out what directory holds your video files so you can properly address them with the FileTransfer plugin.
I'm trying to upload 100 plus photos.. some photos were not uploaded about half of the total number of photos are missing.. is there any other method to do multiple uploads?
You could zip the files first so you just have one file to upload. You could also try to re-upload the ones that fail.
Thanks for the reply Raymond Camden.. how do I do re-upload the ones that fail? can you give me some idea? thanks!
You would use an error handler, note the files that failed, and try again. Perhaps you can keep calling an 'upload' routine until the list of files is 0, or use a sanity check of some sort that stops this after 5 tries.
Thanks! how about zipping the taken photos? do you have any tutorials for that?
There are a few libraries for client-side zipping. I haven't used them in a while, but Google for JavaScript zip library and you will find some options. In general, I don't trust binary heavy stuff in JS, but it's an option.
Thank you for your reply, but, this is doable, right? uploading hundreds of photos to server using web APIs?
Thanks!
... well... maybe. "hundreds" is a lot, and depending on your network connection it may or may not be doable. You *can* look at the connection speed and you can do things like, I have A,B,C to upload, as I process, mark them off the list so that if things go wrong, I can resume later.
I'd say doable - just expect your complexity to go up.
Similarly, how can i browse and read as text then upload a JSON/XML file in cordova app? Could you please post a tutorial? It will be very helpful. Thanks
The filesystem plugin lets you do file system operations. So it can list, read directories and files. To make a 'browser' you would use the plugin and render stuff so the user can navigate.
As for uploading, that's the filetransfer plugin which is officially (afaik) deprecated now that there is support for it in native browser JavaScript (but you can still use the plugin).