Upload progress bar using XHR (Fetch alternative)

Last updated: September 27, 2022.

At the time of writing, you cannot track the upload progress of a POST or PUT request using the Fetch API.

A native alternative is to use the XMLHttpRequest (XHR) object.

Table of contents

HTML and element selection

First, create an input element of type file so a user can select a local file and an upload button.

Then, create a progress bar with a <progress> element. For percentage progress, set the value attribute to 0 and the max attribute to 100.

Select the input element, upload button and progress bar in JavaScript.

<!-- Step 1 of 2: HTML and element selection -->

<!-- Upload -->
<div class="container">
  <input type="file" accept=".png, .jpg" id="file">
  <button id="upload-btn">Upload a file</button>
</div>
<!-- Progress bar -->
<div class="container">
  <progress id="progress-bar" value="0" max="100"></progress><br>
  <label for="progress-bar">0%</label>
</div>

<script>
  const file = document.getElementById('file');
  const btn = document.getElementById('upload-btn');
  const progress = document.getElementById('progress-bar');
</script>

Handling the upload with the XMLHttpRequest object

Inside a click event listener attached to the upload button, you need to select the file.

Optionally, you can append it to a FormData object. This is generally recommended because it will automatically set request headers for you.

Then, you post the payload by creating by making a POST or PUT request using the XMLHttpRequest object.

To measure upload progress, attach a progress event listener to the upload object on the request. Every time there is upload progress, its callback function is fire. You can use information available on the available event object to update the DOM.

The server response is handled inside a callback function listening out for a load event on the request.

/* Step 2 of 2: Handling upload with upload progress updates */

btn.addEventListener('click', function() {
  
  // Select user file from file list (always position 0 for 1 files)
  const userImg = file.files[0];

  // Embed file to FormData object, which will automatically set request headers
  const payload = new FormData();
  payload.append('user-image', userImg, 'user-image.jpg'); // (key, data, new filename)

  /* A POST request using XML object */
  const req = new XMLHttpRequest(); // Initialize request
  req.open('POST', 'https://httpbin.org/post'); // Specify request type and endpoint

  // Add event listener to upload listening for progress. Function fires
  // regularly, with progress contained in "e" object
  req.upload.addEventListener('progress', function(e) {
    // Every time progress occurs
    const percentComplete = (e.loaded / e.total)*100; // Calculate percentage complete via "e" object
    progress.setAttribute('value', percentComplete); // Update value of progress HTML element
    progress.nextElementSibling.nextElementSibling.innerText = Math.round(percentComplete)+"%"; // Prints progress in progress element label as well
  })
  
  // Fires when upload is complete
  req.addEventListener('load', function() {
    console.log(req.status); // Response status code
    console.log(req.response); // Request response
  })

  req.send(payload); // Sends request
});

Testing tips

You may find that the upload progress bar only updates once: from 0 to 100.

This can be because the file upload occurs very quickly.

To simulate a slow internet connection, try adding 'throttling' in the network tab of your browser's developer tools.

With this, you have a better opportunity to see the progress bar in action.

Full code example

Here's the full code example with explanatory comments removed:

<!-- User file input -->
<div class="container">
  <input type="file" accept=".png, .jpg" id="file">
  <button id="upload-btn">Upload a file</button>
</div>

<!-- Progress bar -->
<div class="container">
  <progress id="progress-bar" value="0" max="100"></progress><br>
  <label for="progress-bar">0%</label>
</div>

<script>
  const file = document.getElementById('file');
  const btn = document.getElementById('upload-btn');
  const progress = document.getElementById('progress-bar');

  btn.addEventListener('click', function() {
  
  /* Prepare payload */
  const userImg = file.files[0];
  const payload = new FormData();
  payload.append('user-image', userImg, 'user-image.jpg');

  /* XHR POST request */
  const req = new XMLHttpRequest();
  req.open('POST', 'https://httpbin.org/post');

  req.upload.addEventListener('progress', function(e) {
    const percentComplete = (e.loaded / e.total)*100;
    progress.setAttribute('value', percentComplete);
    progress.nextElementSibling.nextElementSibling.innerText = Math.round(percentComplete)+"%";
  })
  
  req.addEventListener('load', function() {
    console.log(req.status);
    console.log(req.response);
  })

  req.send(payload);
});
</script>

Related links