File uploads used to be difficult to implement for developers. Luckily, as web standards have advanced, so have file uploads. AJAX (XMLHttpRequests) now has ubiquitous browser support, and can be safely relied on to handle file uploads. And even better, the new FormData interface allows you to easily grab all of the form’s keys/values in just a couple of lines.
In this post, you’ll learn how to use current AJAX best practices to upload files to a server. The example below supports uploading multiple files in a single request, but the same technique can be used for single-file uploads as well.
Let’s begin!
Compatibility and Usage
Well actually, before we begin you should know that although AJAX and FormData are compatible with more than 95% of browsers, there’s a chance that you need to support the 5% of users who are using a legacy browser. If you’re not doing that, feel free to ignore this step. But if you need to support browsers like IE9, the best way to check for compatibility is to use object detection:
function supportAjaxUploadWithProgress() {
return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
function supportFileAPI() {
var fi = document.createElement('INPUT');
fi.type = 'file';
return 'files' in fi;
};
function supportAjaxUploadProgressEvents() {
var xhr = new XMLHttpRequest();
return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
};
function supportFormData() {
return !! window.FormData;
}
}
If this function returns true, then you’re good. If not, the older methods can still be used such as traditional form submission.
As a reference, here are the browsers that currently support AJAX file uploads:
IE | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|
10.0+ | 4.0+ | 7.0+ | 5+ | 12+ |
Now, let’s actually begin!
Uploading Files to the Server with AJAX
Now that you know that the client’s browser is compatible, the first thing you need to do is to create 3 HTML elements which will serve as the actual UI for the file upload. Note the multiple
field on the input field, which allows the user to select multiple files by using the CTRL and SHIFT keys. The ID’s will allow you to reference these elements in the next step:
<form id="upload-form" action="handler.php" method="POST">
<input id="file-select-input" multiple="multiple" type="file" />
<button id="upload-button">Upload</button>
</form>
Also keep in mind here that the method
and action
fields for the form are actually not used if the form is sent using AJAX. They are defined there as a fallback in case the browser is not running JavaScript and needs to submit the form in the traditional fashion.
Next, you need to create three variables that hold references to the <form>
. <input>
, and <button>
elements in your HTML:
var form = document.getElementById('upload-form');
var fileSelect = document.getElementById('file-select-input');
var uploadButton = document.getElementById('upload-button');
Then, you need to listen to the form’s onsubmit
event:
form.onsubmit = function(event) {
// Prevent a non-AJAX file upload from starting
event.preventDefault();
// Let the user know that the upload has begun
uploadButton.innerHTML = 'Uploading...';
// The rest of the code will go here...
}
Inside the event listener, you start by calling preventDefault()
on the event object. This will prevent the browser from doing its default behavior which is to treat the upload as a non-AJAX file upload.
Then you update the innerHTML
property on the uploadButton
which allows the user to know that the files have begun to upload.
The next thing is to get the FileList
from the <input>
element and store this in a variable:
// Get the selected files from the input
var files = fileSelect.files;
Then you create a new FormData
object. This constructs the key/value pairs which are used in the data payload for the AJAX request:
// Create a new FormData object
var formData = new FormData();
Next, you take the files in the files
array and add them to the formData
object you just created. This is also a good place to check to make sure the user is uploading the type of file you are expecting:
// Loop through each of the selected files.
for(var i = 0; i < files.length; i++){
var file = files[i];
// Check the file type
if (!/image.*/.test(file.type)) {
return;
}
// Add the file to the form's data
formData.append('myfiles[]', file, file.name);
}
In the above snippet, you use the forEach function to iterate through each file
in the files
array. The file’s type
property will return the file’s MIME type as a string. This is a list of common MIME types. This string is then tested against a regex, and will avoid inserting the file if it is not an image. Then the formData’s append
method is used to add the file to the form.
The myfiles[]
is also important in that this will be the name you will use to access the uploaded files on the server. The name can be anything you choose, and as with regular form data, you can append multiple values with the same name. The extra brackets at the end follow PHP’s naming conventions and allow you to loop through the multi-file upload on the server.
Next, you create a XMLHttpRequest
object that is responsible for communicating with the server:
// Set up the request object
var xhr = new XMLHttpRequest();
Now you create a new connection to the server using the open
method. This method takes 3 parameters: The HTTP method
(ex: GET, POST, PUT, DELETE), the URL that will handle the request, and a boolean value which represents whether the request should be asynchronous:
// Open the connection and pass the file name
xhr.open('POST', 'handler.php', true);
Then, you’ll need to hook up an event listener that will run when the onload
event is fired. Taking a look at the status
property of the xhr
object will let you know if the request was successful:
// Set up a handler for when the request finishes
xhr.onload = function () {
uploadButton.innerHTML = 'Upload';
if (xhr.status === 200) {
// File(s) uploaded
alert('File uploaded successfully');
} else {
alert('Something went wrong uploading the file.');
}
};
Finally, the last thing to do is actually send the request. You need to pass the formData
object to the xhr’s send
method:
// Send the Data.
xhr.send(formData);
And with that, you’ve got a full-fledged AJAX file uploader on the front end.
All together now
Here is what the above code looks like when put together. Keep in mind that trying to upload files using the example will fail as there is no backend that has been configured:
See the Pen
AJAX File Upload Example by Filestack (@Filestack)
on CodePen.
Reading the file from the server
Now that you’ve got the file upload handled on the front end, the next step is to read the data on the server. Depending on your backend stack, you’ll read the file as follows:
- PHP:
file_get_contents("php://input")
- Rails:
request.env['rack.input']
- Django:
request.raw_post_data
Here are some resources to get you started on implementing the backend for your file uploader:
- StackOverflow – jQuery AJAX file upload PHP
- Pluralsight – Handling File Upload Using Ruby on Rails 5 API
- Django – Django File Upload
AJAX feedback events
The above example is a great minimal example, but the user still doesn’t have any feedback on the progress of their upload. Let’s add an event handler that could be used to update a progress bar.
To do so, use the xhr.upload
‘s progress
event. The handler will be run every time the file has made progress in uploading to the server:
// add the code below after xhr is first defined within the form's onsubmit handler
function handleProgressEvent(e){
var percent = evt.loaded / evt.total * 100;
// update progress bar here
}
xhr.upload.addEventListener('progress', onprogressHandler, false);
The snippet above doesn’t include it, but you would add code within the event handler to update a progress bar DOM element using the percent
variable. Keep in mind that the event is set on the xhr.upload
object and NOT on the xhr
object itself.
There are also other useful events that can be useful in providing additional user feedback:
xhr.upload.onloadstart
– the upload beginsxhr.upload.onload
– the upload ends successfullyxhr.upload.onerror
– the upload ends with an errorxhr.upload.onabort
– the upload gets aborted by the user
Alternatives to AJAX File Uploads
If managing your own AJAX file upload system sounds too time-consuming, then an alternative to consider is to use a third party system.
There are a couple of benefits to using a third party which include:
- Implementation – Adding a third party file uploading service is usually as easy as copying and pasting a file upload widget. Spending less time building your own file uploader means more time spent building the rest of your application.
- Security – These services take care of all of the security implications of allowing your users to upload files, and stay up to date with the latest best practices.
- Maintenance – As your users find new ways to break your own file uploader, maintaining your own service will become increasingly challenging.
- Scalability – As more and more users upload files to your system, you may find yourself spending time scaling up your storage infrastructure. Not a problem when you hand it off to a third party.
However, there are tradeoffs to using a third party, which include:
- Control – You have less control with a third party as you do not have direct access to the infrastructure behind file storage.
- Sensitive Data – Third party systems cannot be used for information that must be on-premises, such as sensitive financial or health records.
- Legacy Systems – Up front cost to switch to a third party if there is an existing legacy file upload system can be cost prohibitive.
Summary
In this post you learned how to upload files to a web server using AJAX and FormData. In addition, you learned about the third party systems that exist as an alternative to building it yourself. With this info, you should be able to easily integrate file upload capability into your application.
Further Reading
Filestack is a dynamic team dedicated to revolutionizing file uploads and management for web and mobile applications. Our user-friendly API seamlessly integrates with major cloud services, offering developers a reliable and efficient file handling experience.