Reading local files with JavaScript
Letting users select and interact with files on their own device is a core web interaction — for uploading photos, submitting documents, or processing data entirely in the browser without a round trip to a server. This guide covers the established JavaScript techniques for selecting files, reading their metadata, and loading their content.
The modern approach: File System Access API
For Chromium-based browsers like Chrome and Edge, the File System Access API provides read and write access to files and directories on the user's local system. Because this API isn't available everywhere yet, the browser-fs-access helper library is recommended: it uses the new API where supported and falls back to legacy methods in other browsers.
Selecting files the classic way
There are two main ways to let users pick files: an HTML input element or a custom drag-and-drop zone.
Using the file input element
The <input type="file"> element, supported in every major browser, is the simplest selection mechanism. Clicking it opens the operating system's native file picker. Adding the multiple attribute allows selecting several files at once. After the user makes a selection, a change event fires, and the chosen files are available at event.target.files — a FileList object containing File objects.
<!-- The `multiple` attribute lets users select multiple files. --> <input type="file" id="file-selector" multiple> <script> const fileSelector = document.getElementById('file-selector'); fileSelector.addEventListener('change', (event) => { const fileList = event.target.files; console.log(fileList); }); </script>
To restrict what users can pick, add an accept attribute to the input element, listing the allowed file types. An image editor, for instance, would use it to reject non-image files.
<input type="file" id="file-selector" accept=".jpg, .jpeg, .png">
Building a custom drag-and-drop zone
The <input type="file"> element is itself a drop target in some browsers, but it's small and awkward to use. A better pattern is to keep the input for clicking and add a dedicated, larger drop surface. The drop area can be a specific element or the entire window, as in the image compression app Squoosh, where users can drop an image anywhere on the page.
Enable an element as a drop zone by listening for two events:
dragover— updates the browser UI to show that dropping will copy the file.drop— fires when the user releases the file. The files are available atevent.dataTransfer.files, again as aFileListofFileobjects.
const dropArea = document.getElementById('drop-area'); dropArea.addEventListener('dragover', (event) => { event.stopPropagation(); event.preventDefault(); // Style the drag-and-drop as a "copy file" operation. event.dataTransfer.dropEffect = 'copy'; }); dropArea.addEventListener('drop', (event) => { event.stopPropagation(); event.preventDefault(); const fileList = event.dataTransfer.files; console.log(fileList); });
Calling event.stopPropagation() and event.preventDefault() in these handlers is essential: without them, the browser will navigate away from the page and open the dropped files directly.
Directory selection limitations
Accessing directories from JavaScript remains problematic. The webkitdirectory attribute on the file input lets users pick a folder, but it isn't supported in Firefox for Android or Safari on iOS. If a user drags a directory onto a drop zone, the drop event provides a File object for the directory itself — not for the files it contains.
Reading file metadata
A File object carries basic information about the selected file. Most browsers expose the name, size, and type (MIME) properties, though the exact set can vary by platform and browser.
function getMetadataForFileList(fileList) { for (const file of fileList) { // Not supported in Safari for iOS. const name = file.name ? file.name : 'NOT SUPPORTED'; // Not supported in Firefox for Android or Opera for Android. const type = file.type ? file.type : 'NOT SUPPORTED'; // Unknown cross-browser support. const size = file.size ? file.size : 'NOT SUPPORTED'; console.log({file, name, type, size}); } }
Reading file contents
The FileReader API loads a File object's content into memory. It supports three read modes:
readAsArrayBuffer— for raw binary datareadAsDataURL— for embedding content, such as displaying an imagereadAsText— for plain text files
function readImage(file) { // Check if the file is an image. if (file.type && !file.type.startsWith('image/')) { console.log('File is not an image.', file.type, file); return; } const reader = new FileReader(); reader.addEventListener('load', (event) => { img.src = event.target.result; }); reader.readAsDataURL(file); }
In the example above, a user-selected file is read as a data URL and used as the source of an img element to display it.
Tracking read progress
For large files, a progress indicator improves the experience. The progress event fired by FileReader exposes two properties: loaded (bytes read so far) and total (total bytes to be read).
function readFile(file) { const reader = new FileReader(); reader.addEventListener('load', (event) => { const result = event.target.result; // Do something with result }); reader.addEventListener('progress', (event) => { if (event.loaded && event.total) { const percent = (event.loaded / event.total) * 100; console.log(`Progress: ${Math.round(percent)}`); } }); reader.readAsDataURL(file); }



