As the applications we build with web technologies aspire to match up to native apps, one area where the browser remains challenged is in its access to files on the user’s system. Browsers have, for many years, allowed users to select files to upload to a server in HTML form with <input type="file">
.
But the content of these files has always been hidden from any JavaScript running in the browser. If we wanted to play video files, or edit images that were on the user’s local system, we needed to first upload the file to a server.
If we could read the contents of these files, we could rely less on server-side functionality. Thankfully, with HTML5 we can now read files from the local file system. This means we can create apps that work better offline.

The HTML input element of type="file"
allows users to select one or more files from the local file system. Before HTML5, the purpose of the file input was solely to enable users to select files to be uploaded via a form. In HTML5, the input elements of type="file"
now give developers access to metadata about the files selected: the file name, last modification date, and file size in bytes.
If we want the user to be able to choose multiple files at a time, we need to give the input a multiple
attribute. Then, we’ll create a change event handler for our file input (we’ll give this the ID fileChooser
), called when the value of the file input changes.
-
document.querySelector(“#fileChooser”).addEventListener
-
(‘change’,filesChosen, false)
When the user chooses a file with the file input, our event handler is called and receives the event as an argument. The target attribute of the event is the file input. This has an attribute file, which is an array like object FileList
, containing the file objects selected by the user. Even if we haven’t set a multiple
attribute on our input, the files attribute is still a FileList
, just containing a single item.
Getting data
Now we can iterate over the FileList
, working through each selected file in turn and getting its name, size and last modified date:
-
function filesChosen(evt){
-
var chosenFile = evt.target.files[0]; //get the first file in the FileList
-
var fileName = chosenFile.name; //the name of the file as a string
-
var fileSize = chosenFile.size; //the size of the file in bytes, as an integer
-
var fileModifiedDate = chosenFile.lastModifiedDate; //a Date object
-
}
How might we use these? Well, we could use localStorage
to remember details about files that have been uploaded to a server, and alert users when we have already uploaded a file with the same name, size and modification date, saving them an upload. Or, before uploading, we might determine those files that may take a long time to upload due to their size, and warn the user.
However, this information is still quite limited. Luckily, HTML5 also provides a way of reading the content of a file.
Reading files
The FileReader
object allows us to read the content of a file once we’ve got a file from the user. For security, the user must actively give us the reference to the file in order for it to be readable. There are a number of FileReader
methods for getting a file’s contents:
readAsDataURL(file)
: Returns the contents of the file as adataURL
, which might be used as thesrc
of animg
element, or an image in a style sheetreadAsText(file [,encoding])
: Reads the file as a string, with the given optional encoding (default UTF-8)readAsArrayBuffer(file)
: Reads the contents of a file as anArrayBuffer
Because JavaScript is single-threaded, and files may potentially be large, the FileReader
read methods are asynchronous. This also means multiple files can be read simultaneously. We can stop the reading of a file while it’s in progress using FileReader.abort()
. Because these methods are asynchronous, rather than setting the value of a variable to the result of the method, we need to listen for events that are sent to the FileReader
object.

One of the events the FileReader
receives is loaded
when the file we want has been read. The target property of this event is the FileReader
itself – this has a property called result
, where the contents of the file that was read is contained. Here’s how we would get the dataURL
for the contents of a file:
-
var reader = new FileReader();
-
//create our FileReader object to read the file
-
reader.addEventListener(“load”, fileRead, false);
-
//add an event listener for the onloaded event
-
function fileRead(event){
-
//called when the load event fires on the FileReader
-
var pictureURL = event.target.result;
-
//the target of the event is the FileReader object instance
-
//the result property of the FileReader contains the file contents
-
}
-
reader.readAsDataURL(file);
-
//when the file is loaded, fileRead will be called
We add the event listener for the load event, and in the handler for this event we get the target.result
, which is the value of the FileReader
operation (so, a DataURL
, string or arrayBuffer
, depending on what operation we asked the FileReader
to perform).
We can listen for any of the following events on the FileReader
object:
loadstart
: WhenFileReader
starts reading the fileprogress
: Intermittently while the file is loadingabort
: When the file reading is abortedload
: When reading completes successfullyloadend
: When the file has either been loaded or the read has failed
If a read succeeds, both a load
and loadend
event are fired. If it fails, error
and loadend
events are fired.
Putting it together
We’ll let the user select an image file, then show it as a thumbnail. First, our input element:
-
<input type=“file” id=“chooseThumbnail” accept=“image/*”>
-
and we add an event listener for when the input changes
-
document.querySelector(“#chooseThumbnail”).addEvent
-
Listener(‘change’,showThumbNail, false)
When the user makes a selection, we call the function showThumbNail()
. In HTML5 we can use accept='image/*'
to accept any image type. The target of the event is the input element, which has a property file (an array-like object called a FileList
). We get the first element in the FileList
and read it.
-
function showThumbNail(evt){
-
var url;
-
var file;
-
file = evt.target.files[0];
-
reader = new FileReader();
-
//we need to instantiate a new FileReader object
-
reader.addEventListener(“load”, readThumbNail, false);
-
//we add an event listener for when a file is loaded by the FileReader
-
//this will call our function ‘readThumbNail()’
-
reader.readAsDataURL(file);
-
//we now read the data
-
}
-
function readThumbNail(event){
-
//this is our callback for when the load event is sent to the FileReader
-
var thumbnail = document.querySelector(“#thumbnail”);
-
thumbnail.src= event.target.result;
-
//the event has a target property, the FileReader with a property ‘result’,
-
//which is where the value we read is located
-
}
URLs for Files
Often, when we access a local file, we won’t actually want to use its contents directly, but use the file for some other purpose. For example, if it is an image file we may want to display the image; if it is a CSS file, use it as a stylesheet.
One particular case is displaying a video stream in a video element, when working with getUserMedia
. The simplest way of doing this is to get a temporary, anonymised URL from the browser to use as we would any other URL – for example as the value of an image src
attribute. In the HTML5 File API, the window object has a URL property. This has two methods:
createObjectURL
: Creates a URL for a given filerevokeObjectURL
: Destroys the reference between the URL and the file
We can’t store a URL created in this way in, say, localStorage
then reuse it in a different window, as it only persists for the life of the document while the window that created it is is open.
Here’s how we might use this, in conjunction with an input of type file to get an image from the local file system and display it as a thumbnail. The input element is exactly as before:
-
<input type=“file” id=“chooseThumbnail” accept=“image/*”>
-
and here‘s the JavaScript
-
function showThumbNail(evt){
-
var url;
-
var file;
-
var thumbnail = document.querySelector(“#thumbnail”);
-
//this is the image element where we’ll display the thumbnail
-
file = evt.target.files[0];
-
//because there‘s no ‘multiple‘ attribute set on the input
-
//users can only select one
-
//we’d want to check this is the right sort of file
-
url= window.URL.createObjectURL(file);
-
//we create our URL (for WebKit browsers we need webkitURL.createObjectURL)
-
thumbnail.src=url;
-
//we give our thumbnail image element this URL as its source
-
}
We’re done! No need to worry about asynchronous file reads or callback functions. When we only want to use a file, and not read its contents, this is by far the preferred solution.
Wrapping Up
The File API brings the browser closer to the native file system, allowing users to play, view and edit content from their local system directly in the browser with no need for these to be sent to a server.
It even allows us to access the camera and take photos using capture=camera
as part of the type attribute’s value. We will however still be waiting for some time (quite likely forever) to get full access to the native file system.
Words: John Allsopp
John Allsopp is a web developer and author. This article is taken from his upcoming book – read a draft here.
Liked this? Read these!
- Choose the right Git branching strategy
- Brilliant WordPress tutorial selection
- Free tattoo fonts for designers