Explain Codes LogoExplain Codes Logo

Does HTML5 allow drag-drop upload of folders or a folder tree?

html
drag-and-drop
file-upload
javascript
Anton ShumikhinbyAnton Shumikhin·Jan 25, 2025
TLDR

It's a "yes" from HTML5. It enables folder uploads through drag-and-drop. Deploy the <input type="file"> element with webkitdirectory and directory attributes, allowing users to pick and upload complete folders:

<input type="file" webkitdirectory directory multiple />

Generate a drop event listener, then cycle through dataTransfer.items using webkitGetAsEntry to gain access to the dropped items:

document.addEventListener('drop', e => { e.preventDefault(); let items = e.dataTransfer.items; for (let i = 0; i < items.length; i++) { // Magic happens: dataTransfer items turn into file system entries let entry = items[i].webkitGetAsEntry(); if (entry) { // Here's where the wild entries are handled within handleEntry function handleEntry(entry); } } });

As a rule of thumb, always check for browser support for webkitdirectory due to variability.

The mechanics: Breaking down the drop event

The drop plot thickens when a user drags their folder over the upload area and a drop event awakens. Within your drop handler, here's how the story unfolds:

Putting the brakes on default behavior: This makes it possible for you to intervene with your custom handling of dropped data:

e.preventDefault();

The great traversal: To pick through directories and subdirectories, a recursive function works best, in tandem with webkitGetAsEntry.

Folder content reading: A well-directed breadth-first search (BFS) algorithm synergizes with readEntries to process directories/files, cracking the whip for controlled asynchronous reading.

Bringing async/await to the table: This pair comes to your rescue while making folder traversal and processing readable and manageable.

Taming browser-specific quirks: Remember, readEntries has a ceiling on how many entries it permits reading at a go. A loop routine is a lifesaver when you face directories housing more than 100 items.

Advanced maneuvering of files and folders

FileReader API: For those scenarios when you need to dig into the contents of files, the FileReader API comes bundled with asynchronous read operations for files on the user's system.

Landing the Files and Directories: Within the traversal narrative, make sure to distinguish files from directories to accurately process them:

if (entry.isFile) { // It's a file - handle file as if it's hot! } else if (entry.isDirectory) { // It's a directory - juggling directories now! }

Handling curves: Buckle up for challenges around maximum file sizes and processing hidden files or directories.

Mind the bumps: Handling potential issues and challenges

Managing file size and those pesky hidden files

Security restrictions in HTML5 and browsers restrict the ability to ascertain file sizes before read operations pre-emptively. Also, hidden files or directories might behave inconsistently across platforms. Be ready to test against numerous scenarios for robust functionality.

Support for directory uploads commenced with Chrome version >= 21, and now spans multiple modern browsers. But owing to the non-standard nature of webkitGetAsEntry, keeping an eye out for updates/changes in the API, and extensive testing is warranted.

Utility kits and plugins to the rescue

Consider using NPM packages that specialize in drag-and-drop uploads for stepping up the functionality or browser compatibility. These often include workaround strategies and extended features surpassing native HTML5 capabilities.

An example worth a thousand lines

Here's a more detailed example to demonstrate directory reading and file processing using Promises:

function handleEntry(entry) { if (entry.isFile) { entry.file(file => { // Resurrected a file! Let the processing begin! }); } else if (entry.isDirectory) { const dirReader = entry.createReader(); // Go, Go Gadget Loop! Unleashing a do-while loop to handle more than 100 entries let readEntries = () => { dirReader.readEntries((entries) => { if (entries.length) { entries.forEach(handleEntry); readEntries(); // Just keep looping, just keep looping... } }); }; readEntries(); } }