loading...
Cover image for 10 useful HTML file upload tips for web developers

10 useful HTML file upload tips for web developers

atapas profile image Tapas Adhikary Originally published at blog.greenroots.info ・8 min read

Introduction

The ability to upload files is a key requirement for many web and mobile applications. From uploading your photo on social media to post your resume on a job portal website, file upload is everywhere.

As a web developer, we must know that HTML provides the support of native file upload with a bit of help from JavaScript. With HTML5 the File API is added to the DOM. Using that, we can read the FileList and the File Object within it. This solves multiple use-cases with files, i.e, load them locally or send over the network to a server for processing, etc.

In this article, we will discuss 10 such usages of HTML file upload support. Hope you find it useful.

TL;DR

At any point in time, if you want to play with these file upload features, you can find it from here,

The source code of the demo is in my Github repo. ✋ Feel free to follow as I keep the code updated with examples. Please give a ⭐ if you find it useful.

1. Simple file upload

We can specify the input type as file to use the file uploader functionality in a web application.

<input type="file" id="file-uploader">
Enter fullscreen mode Exit fullscreen mode

An input file type enables users with a button to upload one or more files. By default, it allows uploading a single file using the operating system's native file browser.

On successful upload, the File API makes it possible to read the File object using simple JavaScript code. To read the File object, we need to listen to the change event of the file uploader.

First, get the file uploader instance by id,

const fileUploader = document.getElementById('file-uploader');
Enter fullscreen mode Exit fullscreen mode

Then add a change event listener to read the file object when the upload completes. We get the uploaded file information from the event.target.files property.

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  console.log('files', files);
});
Enter fullscreen mode Exit fullscreen mode

Observe the output in the browser console. Note the FileList array with the File object having all the metadata information about the uploaded file.

image.png

Here is the CodePen for you with the same example to explore further

2. Multiple file uploads

We can upload multiple files at a time. To do that, we just need to add an attribute called, multiple to the input file tag.

<input type="file" id="file-uploader" multiple />
Enter fullscreen mode Exit fullscreen mode

Now, the file browser will allow you to upload one or more files to upload. Just like the previous example, you can add a change event handler to capture the information about the files uploaded. Have you noticed, the FileList is an array? Right, for multiple file uploads the array will have information as,

image.png

Here is the CodePen link to explore multiple file uploads.

3. Know about file metadata

Whenever we upload a file, the File object has the metadata information like file name, size, last update time, type, etc. This information can be useful for further validations, decision-making.

// Get the file uploader by id
const fileUploader = document.getElementById('file-uploader');

// Listen to the change event and read metadata
fileUploader.addEventListener('change', (event) => {
  // Get the FileList array
  const files = event.target.files;

  // Loop through the files and get metadata
  for (const file of files) {
    const name = file.name;
    const type = file.type ? file.type: 'NA';
    const size = file.size;
    const lastModified = file.lastModified;
    console.log({ file, name, type, size, lastModified });
  }
});
Enter fullscreen mode Exit fullscreen mode

Here is the output for single file upload,

image.png

Use this CodePen to explore further,

4. Know about file accept property

We can use the accept attribute to limit the type of files to upload. You may want to show only the allowed types of images to browse from when a user is uploading a profile picture.

<input type="file" id="file-uploader" accept=".jpg, .png" multiple>
Enter fullscreen mode Exit fullscreen mode

In the code above, the file browser will allow only the files with the extension jpg and png.

Note, in this case, the file browser automatically sets the file selection type as custom instead of all. However, you can always change it back to all files, if required.

image.png

Use this CodePen to explore the accept attribute,

5. Manage file content

You may want to show the file content after a successful upload of it. For profile pictures, it will be confusing if we do not show the uploaded picture to the user immediately after upload.

We can use the FileReader object to convert the file to a binary string. Then add a load event listener to get the binary string on successful file upload.

// Get the instance of the FileReader
const reader = new FileReader();

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  const file = files[0];

  // Get the file object after upload and read the
  // data as URL binary string
  reader.readAsDataURL(file);

  // Once loaded, do something with the string
  reader.addEventListener('load', (event) => {
    // Here we are creating an image tag and adding
    // an image to it.
    const img = document.createElement('img');
    imageGrid.appendChild(img);
    img.src = event.target.result;
    img.alt = file.name;
  });
});
Enter fullscreen mode Exit fullscreen mode

Try selecting an image file in the CodePen below and see it renders.

6. Validate file size

As we have seen, we can read the size metadata of a file, we can actually use it for a file size validation. You may allow users to upload an image file up to 1MB. Let us see how to achieve that.

// Listener for file upload change event
fileUploader.addEventListener('change', (event) => {
  // Read the file size
  const file = event.target.files[0];
  const size = file.size;

  let msg = '';

 // Check if the file size is bigger than 1MB and prepare a message.
  if (size > 1024 * 1024) {
      msg = `<span style="color:red;">The allowed file size is 1MB. The file you are trying to upload is of ${returnFileSize(size)}</span>`;
  } else {
      msg = `<span style="color:green;"> A ${returnFileSize(size)} file has been uploaded successfully. </span>`;
  }

  // Show the message to the user
  feedback.innerHTML = msg;
});
Enter fullscreen mode Exit fullscreen mode

Try uploading a file of different sizes to see how the validation works,

7. Show file upload progress

The better usability is to let your users know about a file upload progress. We are now aware of the FileReader and the event to read and load the file.

const reader = new FileReader();
Enter fullscreen mode Exit fullscreen mode

The FileReader has another event called, progress to know how much has been loaded. We can use HTML5's progress tag to create a progress bar with this information.

reader.addEventListener('progress', (event) => {
  if (event.loaded && event.total) {
    // Calculate the percentage completed
    const percent = (event.loaded / event.total) * 100;
    // Set the value to the progress component
    progress.value = percent;
  }
});
Enter fullscreen mode Exit fullscreen mode

How about you try uploading a bigger file and see the progress bar working in the CodePen below? Give it a try.

8. How about directory upload?

Can we upload an entire directory? Well, it is possible but with some limitations. There is a non-standard attribute(at least, while writing this article) called, webkitdirectory that allows us to upload an entire directory.

Though originally implemented only for WebKit-based browsers, webkitdirectory is also usable in Microsoft Edge as well as Firefox 50 and later. However, even though it has relatively broad support, it is still not standard and should not be used unless you have no alternative.

You can specify this attribute as,

<input type="file" id="file-uploader" webkitdirectory />
Enter fullscreen mode Exit fullscreen mode

This will allow you to select a folder(aka, directory),

image.png

User has to provide a confirmation to upload a directory,

image.png

Once the user clicks the Upload button, the uploading takes place. One important point to note here. The FileList array will have information about all the files in the uploaded directory as a flat structure. But the key is, for each of the File objects, the webkitRelativePath attribute will have the directory path.

For example, let us consider a main directory and other folders and files under it,

image.png

Now the File objects will have the webkitRelativePath populated as,

image.png

You can use it to render the folder and files in any UI structure of your choice. Use this CodePen to explore further.

9. Let's drag, drop and upload

Not supporting a drag-and-drop for file upload is kinda old fashion, isn't it? Let us see how to achieve that with a few simple steps.

First, create a drop zone and optionally a section to show the uploaded file content. We will use an image as a file to drag and drop here.

<div id="container">
  <h1>Drag & Drop an Image</h1>
  <div id="drop-zone">
    DROP HERE
  </div>

  <div id="content">
    Your image to appear here..
  </div>

</div>
Enter fullscreen mode Exit fullscreen mode

Get the dropzone and the content areas by their respective ids.

 const dropZone = document.getElementById('drop-zone');
 const content = document.getElementById('content');
Enter fullscreen mode Exit fullscreen mode

Add a dragover event handler to show the effect of something going to be copied,

dropZone.addEventListener('dragover', event => {
  event.stopPropagation();
  event.preventDefault();
  event.dataTransfer.dropEffect = 'copy';
});
Enter fullscreen mode Exit fullscreen mode

image.png

Next, define what we want to do when the image is dropped. We will need a drop event listener to handle that.

dropZone.addEventListener('drop', event => {
  // Get the files
  const files = event.dataTransfer.files;

// Now we can do everything possible to show the
// file content in an HTML element like, DIV
});
Enter fullscreen mode Exit fullscreen mode

Try to drag and drop an image file in the CodePen example below and see how it works. Do not forget to see the code to render the dropped image as well.

10. Handle files with objectURLs

There is a special method called, URL.createObjectURL() to create an unique URL from the file. You can also release it by using URL.revokeObjectURL() method.

The DOM URL.createObjectURL() and URL.revokeObjectURL() methods let you create simple URL strings that can be used to reference any data that can be referred to using a DOM File object, including local files on the user's computer.

A simple usage of the object URL is,

img.src = URL.createObjectURL(file);
Enter fullscreen mode Exit fullscreen mode

Use this CodePen to explore the object URL further. Hint: Compare this approach with the approach mentioned in #5 previously.

Conclusion

I truly believe this,

Many times a native HTML feature may be enough for us to deal with the use-cases in hands. I found, file upload is one such that provides many cool options by default.

Let me know if this article was useful to you by commenting below. You may also like,

The GitHub repo,

GitHub logo atapas / html-file-upload

Useful HTML file upload tips for web developers

HTML File Upload Tips

Useful HTML File Upload Tips for Web Developers. This repository contains the code examples that explains the tips in details.

Feel free to follow this project as I keep updating the source code. If it was useful to you, show your support with a star

Please feel free to clone the repo and try running it locally. This project is also deployed with Netlify and you can see it running from here, https://html-file-upload.netlify.app/

Build Status: Netlify Status



If it was useful to you, please Like/Share so that, it reaches others as well.

You can @ me on Twitter (@tapasadhikary) with comments, or feel free to follow.

Discussion

pic
Editor guide
Collapse
p810 profile image
Payton Bice

Something to note about the progress bar example is that it may not finish for users who have fast connections. I was testing it out and noticed that a progress event was fired around the 1% mark for a couple of files, but wasn't fired again before readyState indicated that the file upload was done. For a production implementation, you'll probably also want to hook into the loadend event to account for this. Here's a slightly modified version of the above example that demonstrates this:

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  const file = files[0];
  reader.readAsDataURL(file);

  reader.addEventListener('progress', updateProgressBar);
  reader.addEventListener('loadend', updateProgressBar);

  function updateProgressBar(event) {
    if (! event.loaded || ! event.total) {
      return;
    }

    const percent = (event.loaded / event.total) * 100;
    progress.value = percent;
    document.getElementById('progress-label').innerHTML = Math.round(percent) + '%';

    if (percent === 100) {
      let msg = `<span style="color:green;">File <u><b>${file.name}</b></u> has been uploaded successfully.</span>`;
      feedback.innerHTML = msg;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
atapas profile image
Tapas Adhikary Author

Great point Payton! I am gonna look into that and modify. Thanks for reading and commenting.

Collapse
danielfinke profile image
Daniel Finke

What specifies the domain of the Object URLs? In the embedded CodePen example #10, the URL ends up something like blob:https://cdpn.io/5a61e13d-0c89-490c-8cce-d6bf06483b9b. Not understanding how it came up with cdpn.io

Collapse
atapas profile image
Tapas Adhikary Author

Hey Daniel, thanks for mentioning that. The object url doesn't mention any domain for this example. It is just expected to get the image converted for this example.

I have rechecked and found it working when I upload an image. Could you please elaborate the steps to get the problem mentioned? I am interested to know that and check.

Collapse
danielfinke profile image
Daniel Finke

Hi Tapas, I was just examining the src attribute of the image in the iframe after the image was uploaded.

I figured out why I was confused though. In your post, the CodePen iframe has the src value https://codepen.io/atapas/embed/BazzaoN?height=600&amp;default-tab=result&amp;embed-version=2. But I didn't notice last time that there was a sub-iframe that CodePen put in with src value https://cdpn.io/atapas/fullembedgrid/BazzaoN?animations=run&amp;type=embed. So it seems like the blob URL is just based on the URL of the page or frame that the JS is running in.

Seems kind of weird that the browser adds the domain in when the blob is local to you machine only anyway

Thread Thread
atapas profile image
Tapas Adhikary Author

Sure, thanks Daniel.

Collapse
johnhorner profile image
John Horner

This is very interesting but a true upload would write the file out to a server, with all the complexities that involves. File name rules, permissions, paths, umasks etc. Using this code what have we actually uploaded to? The browser’s memory?

Collapse
atapas profile image
Tapas Adhikary Author

Yes John, you are right.

The scope of the article was to introduce the basics and focusing on the Html file upload tag and client side file object.

I am writing a series now to build on top of it to showcase with server side pieces using Node.js. Hoping that will get you what you are asking for. Please stay tuned.

Thanks for reading and commenting!

Collapse
yagizhan14 profile image
Yagizhan Avci

Thank you so much for the explanation.

Collapse
atapas profile image
Tapas Adhikary Author

Thank you very much.. Glad it was useful!

Collapse
olsard profile image
olsard

Great, thanks for sharing!

Collapse
atapas profile image
Collapse
mateenui profile image
Mateen Gbadamosi

Thanks a lot for this. Best article I've read today

Collapse
atapas profile image
Tapas Adhikary Author

Thank you Mateen!

Collapse
nicklleite profile image
Nicholas C-137 🇩🇪

Think this is the best HTML article ive read.

Collapse
atapas profile image
Tapas Adhikary Author

Yaay... That's huge! Thank you very much.

Collapse
jfbrennan profile image
Jordan Brennan

Good tips! Totally agree on leveraging native HTML and web APIs. We need more vanilla!

Collapse
atapas profile image
Tapas Adhikary Author

Thanks Jordan.

Collapse
chidioguejiofor profile image
Chidiebere Ogujeiofor

Wonderful article!

Collapse
atapas profile image
Tapas Adhikary Author

Thank you very much!

Collapse
zeshama profile image
Natnael Getenew

Learned a lot! Thanks

Collapse
atapas profile image
Tapas Adhikary Author

Thank you Natnael. Very glad as it was useful to you!

Collapse
brianmanden profile image
Brian Juul Andersen

Good article. Thank you 🙂👍

Collapse
atapas profile image
Tapas Adhikary Author

Thank you, Brian!

Collapse
artydev profile image
artydev

Thank you very much

Collapse
n0sys73m1ss4f3 profile image
z3r0

Great artical, Thanks so much :)

Collapse
atapas profile image
Tapas Adhikary Author

Thank you for reading and liking!

Collapse
shaijut profile image
ShaijuT

Very useful post, 😄

Collapse
atapas profile image
Tapas Adhikary Author

Glad it was useful, Shaijut.

Collapse
pavelloz profile image
Paweł Kowalski

Heh, file upload has so many weird quirks, especially when dealing with images, i just use Uppy and go on with more important tasks ;)

Collapse
neelam28 profile image
Neelam

Thanks so much for this post. I now have a much better understanding of the file upload system.

Collapse
atapas profile image
Tapas Adhikary Author

Wow, Thanks Neelam! Glad it was useful.

Collapse
onurcelik profile image
Onur Celik

good one 👍🏻✨

Collapse
kritikaramesh profile image
kritikaramesh

Thankyou so much! 😇

Collapse
atapas profile image
Collapse
ghkobbs profile image
Maxwell Morrison

Thank you for such an amazing tips.

Collapse
atapas profile image
Tapas Adhikary Author

Thanks Maxwell.. Glad it was helpful.