Developing an HTML5 file uploader with a PHP back end

HTML5 has brought lots of tools and methods to create web apps that work fast like desktop apps with no page reload. For example, WebSocket lets developers organize bidirectional real-time communication between a client and server to catch events and update states with no traditional requests and responses that take time to refresh the webpage. <audio> lets you play audio files in your browser and control them via Javascript API, and <video> does the same thing with videos. (When that became possible, it became very popular to have a video background on a website.)

Another important thing that HTML5 brought to the table was advanced file uploading and Javascript API to work with files. In this article, we’re going to make a DIY HTML5 file uploader and compare it to a ready-made HTML5 solution.

DIY file uploader objectives

The goal here is not to create a feature-rich, 100% bulletproof file uploader. Instead, we’re going to develop a basic uploader and then see how we can extend it. Here’s what we’re going to do:

HTML5 File UploaderHTML5 File Uploader with a progress bar, dynamic file name display and a drag-and-drop section.

Developing the core functionality

First things first: let’s determine the minimal requirements for our file uploader. There are lots of things you can do with modern HTML and JS, but here are the two priorities for us:

  • Let the user select a file from their file system.
  • Implement file uploading and saving files on the server.

Creating a template

Using <input type="file"> allows the user to select a file from their file system on the front end. We’re going to utilize this input type, as well as a button to asynchronously upload files. Let’s start with the following as a template:

<!DOCTYPE html>
<html>
<head>
  <style>
    html {
      font-family: sans-serif;
    }
  </style>
</head>
<body>
  <h2>DIY HTML5 File Uploader</h2>
  <input type="file" name="file_to_upload" id="file_to_upload">
  <hr>
  <input type="button" value="Upload To Server" id="upload_file_button">
</body>
</html>

As a result, the browser will render just a simple interface with almost no styling (except the font, which is my personal preference). Here’s the output:

HTML5 uploader interfaceOnly two control elements for now, but sufficient to do basic uploading.

Preparing a file for uploading

Since we’re working not with a regular form that sends data to a server via a Submit button, we need to extract the file from the field and send it manually later. For this tutorial, I decided to store the file information in window. Let’s add this code before the closing </body> tag:

 <script>
  document.getElementById('file_to_upload').addEventListener('change', (event) => {
    window.selectedFile = event.target.files[0];
  });

  document.getElementById('upload_file_button').addEventListener('click', () => {
    uploadFile(window.selectedFile);
  });
</script>

Once the value of file_to_upload is changed (meaning that the user has selected a file), we retrieve the file and store it in window.selectedFile for further manipulations from other functions.

In turn, when the upload_file_button is clicked, we send the file to the function that will upload the file to a server.

Uploading the file to a server

As I mentioned before, we aren’t sending the form in the way the browser does by default. Instead, we’re going to add the file to a FormData object and then send it to a server using good old XMLHttpRequest. This is being done in the uploadFile function that I mentioned in the previous step. Here’s the code. Add it before the closing </script> tag:

function uploadFile(file) {
  const formData = new FormData();
  formData.append('file_to_upload', file);

  const ajax = new XMLHttpRequest();
  ajax.open('POST', 'uploader.php');
  ajax.send(formData);
}

The function receives a file as an argument, adds it to the formData object, and sends it to uploader.php via AJAX. Speaking of PHP, let’s enter the back-end territory.

Processing a file on the backend using PHP

In the root directory of the project, create a new folder named uploads to store the uploaded files. Next, create a file called uploader.php, which will handle receiving and moving the files. Here's the code you need to include:

$file_name = $_FILES['file_to_upload']['name'];
$file_temp_location = $_FILES['file_to_upload']['tmp_name'];

if (!$file_temp_location) {
  echo 'ERROR: No file has been selected';
  exit();
}

if (move_uploaded_file($file_temp_location, "uploads/$file_name")){
  echo "$file_name upload is complete";
} else {
  echo 'A server was unable to move the file';
}

Above, you can see a little PHP script that:

  1. Gets all the necessary file information, such as the client’s filename and the temporary location once the file has been received by the server;
  2. Checks if the file has actually been selected (i.e., the respective variable is not empty);
  3. Moves the file to a folder we define (in this case, “uploads”).

Testing basic file uploading

Let’s select a file from the file system using the Choose File input field and then click the Upload To Server button. If you do this with your DevTools Network tab open, you’ll see a POST request that actually sends binary file data to the server. I selected an image from my computer and here’s how it looks:

POST request with binary file dataA network request with the file tracked down using DevTools.

To see if the file reached its destination on the server, let’s just check what’s inside our uploads/ folder:

A folder with an image insideThe file has been uploaded with the same name.

Defining accepted file types

Say you’re building a form that has a file uploader that uploads screenshots of a particular app. A good practice is to narrow down the set of possible file types to images only. Let’s use the most common ones: JPEG and PNG. To do this on the front end, you can add an accept attribute to the file input:

<input type="file" name="file_to_upload" id="file_to_upload" accept=".jpg,.png">

This will alter the system file selection dialog window to allow the user to select only the file types that you put into the attribute. On Windows, you can see this in the bottom right of the window after clicking the Choose file button:

Windows file upload windowFile extensions shouldn’t always be grouped by a file content type. You can also put other extensions there, such as audio and video.

While it is pretty easy to do on the front end, I’d recommend you take it seriously when implementing back-end file type filtering for a production-ready solution.

Progress bar and displaying the file name

Our DIY uploader works, but it is lacking some verbosity. When uploading a larger file, no response might be misleading, so the user may close the page before the upload is complete. To improve the experience with our uploader, let’s add a progress bar and progress percentage, and display the file name as a bonus: we will need it later anyway.

Adding new HTML code

Starting with HTML, put the following lines of code just above our Upload to Server button:

<p id="file_name"></p>
<progress id="progress_bar" value="0" max="100" style="width: 400px;"></progress>
<p id="progress_status"></p>
  • file_name will display the file name
  • progress_bar is an HTML5 tag that will display the uploading progress visually
  • progress_status is used to add a text description to the progress bar

Now that we have the new elements set up, let’s bind JS to them from top to bottom.

Displaying the file name in a separate element

We need to display the file name in the actual file transfer panel. To do this, extend our file_to_upload event listener with one string to make it look like this:

document.getElementById('file_to_upload').addEventListener('change', (event) => {
  window.selectedFile = event.target.files[0];
  document.getElementById('file_name').innerHTML = window.selectedFile.name;
});

Monitoring file upload progress

Next, we need to start monitoring the file uploading progress. This will require us to have our XMLHttpRequest() object initialized. So, insert a new line into the uploadFile function adding a new event listener, like the following:

 function uploadFile(file) {
  const formData = new FormData();
  formData.append('file_to_upload', file);

  const ajax = new XMLHttpRequest();
  ajax.upload.addEventListener('progress', progressHandler, false);
  ajax.open('POST', 'uploader.php');
  ajax.send(formData);
}

Now that we’ve mentioned the progressHandler function in the listener, let’s create it:

function progressHandler(event) {
  const percent = (event.loaded / event.total) * 100;
  document.getElementById('progress_bar').value = Math.round(percent);
  document.getElementById('progress_status').innerHTML = Math.round(percent) + '% uploaded';
}

This function calculates the actual percentage. After that, the value is assigned to both the progress bar and the progress status elements.

Testing file uploading status

With help of DevTools (I used it to throttle my local installation), let’s select a file again and see how the uploading process looks now:

HTML5 File Uploader with a progress barCaught the progress bar on its way to 100%.

Creating a drag and drop region

Since the release of HTML5, people have been using Drag and Drop functionality extensively, especially for uploading files. This way, you can drag a file into a certain region on a webpage and have the file processed. Let’s implement it as the last feature of our DIY HTML5 file uploader.

HTML for the drag and drop region

Technically, it’s possible to put the region anywhere on the page, but I found it intuitive to place it right under the classic upload field. Put the following code below the regular file selector and above <hr>:

<h3>Drag & Drop a File</h3>
<div id="drop_zone">
  DROP HERE
</div>

Styling the region

Let’s have a 400px square with centered text inside. To do that, put the following code just before the closing </style> tag:

div#drop_zone {
  height: 400px;
  width: 400px;
  border: 2px dotted black;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  font-family: monospace;
}

Now that we have the HTML and CSS set up, let’s take a look at the result:

An HTML5 File Uploader with Drag and DropWe’ve defined an area to drag files into.

Coding drag and drop functionality

Our goal here is to monitor dragging and dropping events, extract the data and connect it to our window.selectedFile medium from the first step. Add this code to the <script> and find the detailed description in the code comments:

// Getting our drop zone by ID
const dropZone = document.getElementById('drop_zone');

if (window.FileList && window.File) {
  dropZone.addEventListener('dragover', event => {
    event.stopPropagation();
    event.preventDefault();

    // Adding a visual hint that the file is being copied to the window
    event.dataTransfer.dropEffect = 'copy';
  });

  dropZone.addEventListener('drop', event => {
    event.stopPropagation();
    event.preventDefault();

    // Accessing the files that are being dropped to the window
    const files = event.dataTransfer.files;

    // Getting the file from uploaded files list (only one file in our case)
    window.selectedFile = files[0];

    // Assigning the name of file to our "file_name" element
    document.getElementById('file_name').innerHTML = window.selectedFile.name;
  });
}

Testing drag and drop uploads

The key goal of this functionality is to assign a file for the upload. After that, everything should go the same way as it does with the usual file selection field. Let’s drag a file into the region, see if the name appears, and upload it to the server:

HTML5 Drag and Drop file uploaderThe file that I dragged into the region is being uploaded. Notice how the file name has been set, even though the "Choose File" is still empty.

Full code

At this step, we can consider our prototype ready. Here’s the full code:

<!DOCTYPE html>
<html>
<head>
  <style>
    html {
      font-family: sans-serif;
    }

    div#drop_zone {
      height: 400px;
      width: 400px;
      border: 2px dotted black;
      display: flex;
      justify-content: center;
      flex-direction: column;
      align-items: center;
      font-family: monospace;
    }
  </style>
</head>
<body>
  <h2>DIY HTML5 File Uploader</h2>
  <input type="file" name="file_to_upload" id="file_to_upload" accept=".jpg,.png">

  <h3>Drag & Drop a File</h3>
  <div id="drop_zone">DROP HERE</div>

  <hr>

  <p id="file_name"></p>
  <progress id="progress_bar" value="0" max="100" style="width:400px;"></progress>
  <p id="progress_status"></p>

  <input type="button" value="Upload To Server" id="upload_file_button">

  <script>
    document.getElementById('file_to_upload').addEventListener('change', (event) => {
      window.selectedFile = event.target.files[0];
      document.getElementById('file_name').innerHTML = window.selectedFile.name;
    });

    document.getElementById('upload_file_button').addEventListener('click', () => {
      uploadFile(window.selectedFile);
    });

    // Getting our drop zone by ID
    const dropZone = document.getElementById('drop_zone');
    if (window.FileList && window.File) {
      dropZone.addEventListener('dragover', event => {
        event.stopPropagation();
        event.preventDefault();

        // Adding a visual hint that the file is being copied to the window
        event.dataTransfer.dropEffect = 'copy';
      });

      dropZone.addEventListener('drop', event => {
        event.stopPropagation();
        event.preventDefault();

        // Accessing the files that are being dropped to the window
        const files = event.dataTransfer.files;

        // Getting the file from uploaded files list (only one file in our case)
        window.selectedFile = files[0];

        // Assigning the name of file to our "file_name" element
        document.getElementById('file_name').innerHTML = window.selectedFile.name;
      });
    }

    function uploadFile(file) {
      const formData = new FormData();
      formData.append('file_to_upload', file);

      const ajax = new XMLHttpRequest();
      ajax.upload.addEventListener("progress", progressHandler, false);
      ajax.open('POST', 'uploader.php');
      ajax.send(formData);
    }

    function progressHandler(event) {
      const percent = (event.loaded / event.total) * 100;
      document.getElementById('progress_bar').value = Math.round(percent);
      document.getElementById('progress_status').innerHTML = Math.round(percent) + '% uploaded';
    }
  </script>
</body>
</html>

Should I consider using a ready-made file uploader?

It depends on what you already have and how much time and effort — or money in case you’ve hired a team — you’re willing to invest into making the uploader. The solution we’ve developed in the previous chapter works, but while getting it ready for a production release, you may stumble upon the following pitfalls, among others:

  • Infrastructure: How many users are going to upload files simultaneously? How much storage space is needed? What about setting up a CDN for mirroring uploaded files for different locations?
  • UI/UX: I hope it was fairly easy to understand how to work with the uploader during the explanation, however it is important to have a user-friendly uploader, even for non-tech-savvy people. And what if a new feature you’re planning conflicts with the design you already have?
  • Browser support: While many people tend to update software, others may stick to older browsers that may not support the full potential of what modern technology has to offer.
  • Security: Uploading user-generated content has potential risks. We can’t be 100% sure what’s inside a file at first glance, even if it appears to be an image.

Uploadcare is a fast and secure end-to-end file platform. The company has taken care of all the pitfalls I mentioned above (and more), and developed File Uploader. It was made with developers in mind, which means you can set it up and integrate with more than 35 platforms in minutes. Plus, you don’t have to worry about maintenance or support.

There are also even more powerful features that it offers (such as editing images on the fly and more). Check out the product page to see everything.

Without further ado, let’s see Uploadcare’s File Uploader in action and recreate our uploader’s functionality.

Using Uploadcare’s File Uploader to recreate our existing one

Prerequisites

To follow the tutorial below, you will need to have an Uploadcare account. The free account will cover our needs just fine; you can sign up here.

Also, you will need to obtain your Public Key in the Dashboard.

Integration

The File Uploader widget comes in the form of a tiny JavaScript library that you need to embed into your project. We’ll go with a CDN installation. Include the following code into the <head> of your page:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@v1/web/uc-file-uploader-regular.min.css"
>
<script type="module">
  import * as UC from 'https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@v1/web/file-uploader.min.js';
  UC.defineComponents(UC);
</script>

After that, put the following code into the <body> tag:

<uc-config
  ctx-name="my-uploader"
  pubkey="YOUR_PUBLIC_KEY"
></uc-config>
<uc-file-uploader-regular ctx-name="my-uploader"></uc-file-uploader-regular>

Don’t forget to replace YOUR_PUBLIC_KEY with your actual Public API key.

Here’s the whole code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Uploadcare File Uploader</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@v1/web/uc-file-uploader-regular.min.css"
    >
    <script type="module">
      import * as UC from 'https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@v1/web/file-uploader.min.js';
      UC.defineComponents(UC);
    </script>
  </head>

  <body>
    <uc-config
      ctx-name="my-uploader"
      pubkey="YOUR_PUBLIC_KEY"
    ></uc-config>

    <uc-file-uploader-regular ctx-name="my-uploader"></uc-file-uploader-regular>
  </body>

</html>

As a result, a custom Choose a file button will appear on the page leading to the upload dialog when clicked:

Uploadcare File Uploader widgetThe File Uploader with a clean UI and many uploading sources.

Back end

There is no need to attach custom back-end file handlers when using Uploadcare. Uploaded files are transferred into your project’s storage, and you can reference them by unique ID (UUID) later.

Accepted file types

The File Uploader allows you to restrict uploading certain file types. You can set up a custom message if users try to upload, let’s say, an *.sh file instead of an document or PDF.

When creating our DIY uploader, we added an attribute right within the HTML. Uploadcare’s widget works in a different and more flexible way.

When a file is uploaded, it goes through validators that have been assigned to the widget. You can think of these as filters. Some of them are already predefined, and you can create custom ones along the way.

To limit the accepted file types, first we need to define a message that users will see if they upload a wrong type. Do this by defining a new constant right before registering the Uploadcare components:

const config = document.querySelector('uc-config');
config.localeDefinitionOverride = {
  en: {
    'file-is-not-a-document-type': 'File is not a PDF or document',
  }
};

The text messages above will be used when trying to upload a wrong file type.

Now, let’s add a validator to monitor the file. Here’s the code you need to add before closing the </body> tag, comments included:

<script>
// Create a custom validator
const imageValidator = (outputEntry, api) => {
  // Defining allowed file types
  const types = ['PDF', 'DOCX', 'DOC'];

  // Checking if the file has a name
  if (outputEntry.name === null) {
    return;
  }

  // Getting file extension
  const extension = outputEntry.name.split('.').pop().toUpperCase();

  // Check if the file type is allowed
  if (types.indexOf(extension) === -1) {
    return {
      message: api.l10n('file-is-not-a-document-type'),
      payload: { entry: outputEntry },
    };
  }
};

// Get the widget and assign a new validator to it
const config = document.querySelector('uc-config');
config.fileValidators = [imageValidator];
</script>

Uploading a PNG file will result in the following message:

Uploadcare File Upload Widget displaying custom messageFile Uploader modal is now reacting via the custom validator.

You can create/re-use different validators, such as file size, etc.

Drag and drop, upload progress, file name

All these features are basic features of Uploadcare File Uploader, so there’s no need to develop any of them. However, you can customize the File Uploader’s look and behavior to suit your needs.

Bottom line

The modern Web offers a wider-than-ever range of possibilities! In this article, we created a simple file uploader using some HTML5 features to demonstrate the concept.

Uploadcare, on the other hand, provides an integration-ready and modern file uploading solution for developers, so you don’t need to reinvent the wheel and spend resources creating your own DIY file uploader.

Build file handling in minutesStart for free

Ready to get started?

Join developers who use Uploadcare to build file handling quickly and reliably.

Sign up for free