How to Support Folder Uploads in the Browser
browser-apijavascriptuploadsdirectoriesfrontend

How to Support Folder Uploads in the Browser

AAlex Morgan
2026-06-12
10 min read

A practical guide to supporting folder uploads in the browser with relative paths, validation, compatibility planning, and fallback behavior.

Folder uploads look simple in the interface, but they introduce a different class of browser, UX, and backend concerns than single-file uploads. This guide shows a practical workflow for supporting folder uploads in the browser, from choosing the right input method to preserving relative paths, validating files, handling fallback behavior, and handing the upload off to your server or cloud storage layer. If you are building an asset uploader, static site importer, backup tool, CMS migration flow, or any app that needs users to submit nested files, this gives you a process you can implement and revisit as browser support evolves.

Overview

If your app needs more than one file at a time, users will often expect to upload a whole folder. That expectation is common in image libraries, code importers, documentation tools, design handoff systems, and browser-based developer tools that analyze project structures. The challenge is that the web platform does not expose local directories in exactly the same way across all browsers and APIs.

In practice, most folder upload browser implementations rely on a file input with the webkitdirectory attribute. Despite the name, it is widely used as the main path for directory selection in modern browsers. When a user chooses a folder, the browser returns a flat FileList, and each file usually includes a relative path such as src/components/Button.js. Your job is to reconstruct useful structure from that list, validate it, and send it to the server in a form that preserves hierarchy.

That leads to three design principles:

  • Treat folder upload as a structured batch operation, not just a larger file picker.
  • Assume support is uneven and provide fallback behavior for browsers that do not allow directory picking.
  • Preserve relative paths deliberately from browser to backend, or the folder context is lost.

The implementation path is usually straightforward if you split it into stages: selection, inspection, validation, packaging, transfer, and recovery. That workflow also helps you connect browser logic to backend storage rules and security checks.

Step-by-step workflow

Here is a reliable workflow for building upload directory JavaScript support without turning the UI into a one-off experiment.

1. Start with a directory-capable input

The most common pattern is an HTML file input configured for directory selection:

<input id="folderInput" type="file" webkitdirectory multiple />

Two details matter here:

  • webkitdirectory allows the user to select a folder instead of manually choosing files.
  • multiple is typically included because the browser returns many files from that directory tree.

In JavaScript, you can listen for changes and inspect the returned files:

const input = document.getElementById('folderInput');

input.addEventListener('change', (event) => {
  const files = Array.from(event.target.files || []);
  for (const file of files) {
    console.log(file.name, file.webkitRelativePath || file.name);
  }
});

The key property is usually webkitRelativePath. That path is what lets you rebuild the folder structure later. If you ignore it and upload only file names, two files with the same name from different subfolders can collide.

2. Normalize the returned file list into a tree or manifest

The browser gives you a flat list. Most apps need either a visual tree for display or a manifest for upload. Build one early so validation and user feedback are easier.

function buildManifest(files) {
  return files.map(file => ({
    file,
    path: file.webkitRelativePath || file.name,
    size: file.size,
    type: file.type || 'application/octet-stream'
  }));
}

If your UI needs a nested preview, split paths on / and create a tree structure. If not, a flat manifest with paths is often enough and simpler to maintain.

This is also a good time to calculate batch-level information:

  • total file count
  • total upload size
  • maximum file size
  • number of nested directories
  • detected file types or extensions

Those values help you decide whether to proceed in one request, multipart requests, or chunked upload logic. If you are designing the transport layer, it is worth reviewing related architectural decisions such as Chunked Upload vs Multipart Upload vs Single Request: When to Use Each.

3. Validate before upload, not after failure

Folder uploads can hide a lot of unwanted content: temporary files, hidden files, unsupported extensions, oversized media, generated build output, or accidentally included secrets. Validate in the browser before you spend time and bandwidth sending the batch.

Common checks include:

  • allowed extensions or MIME types
  • blocked file names such as system files or editor metadata
  • maximum file size per item
  • maximum total batch size
  • maximum depth or number of files
  • disallowed paths such as upload roots that include private folders

For example:

const allowed = new Set(['png', 'jpg', 'jpeg', 'json', 'md', 'txt']);

function getExtension(path) {
  const last = path.split('.').pop();
  return last ? last.toLowerCase() : '';
}

function validateManifest(manifest) {
  const errors = [];

  for (const item of manifest) {
    const ext = getExtension(item.path);
    if (!allowed.has(ext)) {
      errors.push(`Unsupported file type: ${item.path}`);
    }
    if (item.size > 10 * 1024 * 1024) {
      errors.push(`File too large: ${item.path}`);
    }
  }

  return errors;
}

Client-side validation should improve the experience, but server-side validation still has to exist. For a broader checklist, see How to Validate Uploaded Files in the Browser Before Sending.

4. Decide how to preserve paths during transfer

This is where many otherwise functional folder upload HTML implementations break down. Your backend needs a clear way to receive both file bytes and relative path metadata.

You have a few workable patterns:

  • Multipart form data: append each file and include its relative path in the field name or in a parallel manifest.
  • JSON manifest plus binary uploads: upload metadata first, then upload each file against a generated upload session.
  • Direct-to-cloud uploads: request presigned upload targets per path, then send files directly to object storage.

A simple multipart approach might look like this:

async function uploadManifest(manifest) {
  const formData = new FormData();

  manifest.forEach((item, index) => {
    formData.append(`files[${index}]`, item.file);
    formData.append(`paths[${index}]`, item.path);
  });

  return fetch('/api/upload-folder', {
    method: 'POST',
    body: formData
  });
}

Do not rely on the original file name alone. Send the relative path explicitly. If you use cloud storage or signed upload flows, map each relative path to a safe storage key rather than trusting user input directly. The architecture tradeoffs are similar to those covered in Direct-to-Cloud Upload Architecture: Pros, Cons, and Decision Checklist and Presigned URL Uploads: Security Risks, Expiration Rules, and Common Mistakes.

5. Provide realistic progress and recovery behavior

A folder upload can contain hundreds or thousands of files. Users need more than a spinner. Show:

  • total file count
  • completed file count
  • total bytes sent
  • current file or current stage
  • validation failures separated from transport failures

If uploads can be interrupted, add idempotency or file-level deduplication so a retry does not create duplicates. That matters more with batches than with single files. A useful companion pattern is described in How to Handle File Upload Retries Without Creating Duplicates.

For the progress UI itself, keep your reporting conservative and understandable. Avoid progress bars that jump to 100% before server-side processing finishes. If your pipeline includes scanning, indexing, or path normalization after transfer, represent that as a separate stage. See Upload Progress Bars That Users Trust: UX Patterns and Edge Cases.

6. Build a fallback path for unsupported environments

Not every browser or browsing context will handle directory selection the same way. Some environments may not support folder picking at all; others may return different metadata. A practical fallback strategy usually includes one or more of the following:

  • allow multiple file selection without folder structure
  • accept a zip archive and unpack it server-side
  • offer drag-and-drop if your app can inspect dropped items
  • show a clear note that full folder hierarchy requires a compatible browser

The important point is not perfect parity. It is graceful degradation. Users should still be able to complete the task, even if the experience is less convenient.

7. Sanitize and reconstruct safely on the server

When the upload reaches the backend, treat all paths as untrusted input. Normalize separators, remove traversal attempts, reject empty segments, and map the resulting path into a controlled upload root. Never write files directly based on user-provided paths without normalization.

Server-side steps often include:

  • path normalization
  • collision handling
  • malware or content scanning
  • type verification
  • metadata storage
  • post-processing or indexing

For security-sensitive workflows, scanning may happen before files become available to other users or downstream systems. If that is part of your pipeline, the UX concerns are similar to those in How to Scan Uploaded Files for Malware Without Breaking UX.

Tools and handoffs

The most reliable folder upload implementations are explicit about where responsibility changes hands. That avoids a common problem: assuming the browser, API, and storage layer all understand folder structure in the same way.

A practical handoff model looks like this:

  • Browser UI: collect files, show folder preview, validate obvious issues, package manifest.
  • Upload API: create an upload session, enforce limits, validate metadata, return accepted paths or signed targets.
  • Storage layer: store file bytes under normalized keys, keep path metadata, trigger background jobs if needed.
  • Application layer: reconstruct logical folders for display, import, processing, or publishing workflows.

That handoff matters because local folders are not the same as server folders. In many systems, you are not truly recreating a filesystem. You are creating a set of object keys or database records that represent a directory-like structure. Treat it as an application-level model, not a direct mirror of the user’s machine.

It is also useful to choose one source of truth for the hierarchy:

  • If the relative path string is the source of truth, make every downstream system preserve it.
  • If a manifest document is the source of truth, version it and validate it before upload begins.
  • If the storage key mapping is the source of truth, persist the original relative path separately for auditing and UI display.

For teams building browser based dev tools, this separation is especially valuable. The same folder import flow may later support client-side analysis, code linting, content extraction, or build previews. A clean manifest model makes those reuse cases easier.

Quality checks

Before you call the feature done, run a checklist that covers real-world edge cases rather than only happy-path demos.

Browser and selection checks

  • Does the picker work in your target browsers?
  • What happens if the user selects an empty folder or a folder with no allowed files?
  • Do hidden files appear, and if so, how does your UI handle them?
  • Does the app clearly explain when folder selection is not supported?

Path integrity checks

  • Are relative paths preserved for every file?
  • Do duplicate file names in different subfolders remain distinct?
  • Are separators normalized consistently?
  • Can your backend safely reject traversal-like paths or malformed segments?

Validation checks

  • Are blocked file types rejected before upload?
  • Are oversized files reported clearly?
  • Do you validate both per-file and total-batch limits?
  • Are server-side checks at least as strict as client-side checks?

Performance checks

  • How does the UI behave with hundreds or thousands of files?
  • Does manifest creation block the main thread for too long?
  • Do progress updates remain responsive during long uploads?
  • Have you tested transport options against realistic payloads?

If upload speed and payload shape are becoming a bottleneck, review the broader system constraints in File Upload Performance Benchmarks: What Slows Uploads Down.

UX checks

  • Can users review the selected structure before uploading?
  • Are failures grouped in a way that makes retrying practical?
  • Does the app distinguish validation errors from network errors?
  • Can users retry only failed items instead of repeating the full batch?

Security checks

  • Are file names and paths sanitized?
  • Are executable or risky types handled according to your app’s rules?
  • Is malware scanning part of the post-upload pipeline where appropriate?
  • Are direct upload permissions scoped only to intended keys or prefixes?

If your folder upload feature often includes images, media, or user-generated content, it is also worth aligning file validation with content-specific practices such as those in Best Practices for Uploading Images on the Web: Size, Format, Compression, and Metadata.

When to revisit

Folder upload support is the kind of feature that works well for a long time, then quietly drifts out of date as browser APIs, storage architecture, and user expectations change. Revisit your implementation when any of the following happens:

  • Browser support changes: if a new API becomes viable or older behavior shifts, re-test your selection and path logic.
  • Your upload scale changes: moving from dozens of files to tens of thousands may require a different batching or chunking strategy.
  • Your backend model changes: switching from local disk to object storage, or from synchronous processing to asynchronous jobs, affects how paths and progress should work.
  • Your security model changes: stricter scanning, retention, or access-control rules often require different upload session design.
  • Your product scope expands: if folder uploads begin feeding importers, build tools, or asset pipelines, validate that your manifest still captures enough metadata.

A simple maintenance routine helps:

  1. Test folder selection in your supported browsers.
  2. Verify that webkitRelativePath or your equivalent path source still behaves as expected.
  3. Run a large nested test folder through the full flow.
  4. Confirm that retries, duplicate handling, and path normalization still work.
  5. Review user-facing errors and progress messages for clarity.
  6. Re-check your upload API contract so browser and backend assumptions stay aligned.

If you are implementing this now, the practical path is: start with <input type="file" webkitdirectory multiple>, build a manifest around relative paths, validate before transfer, preserve path metadata through the API, and offer a fallback such as zip upload or multi-file selection when directory picking is unavailable. That gives you a folder upload browser feature that is useful today and maintainable when the surrounding platform changes.

For teams refining the broader upload flow, related topics worth reviewing next are File Upload API Design Best Practices: Endpoints, Metadata, and Webhooks and How to Validate Uploaded Files in the Browser Before Sending. Folder uploads succeed when the browser UI, transfer model, and backend contract are designed together rather than treated as separate problems.

Related Topics

#browser-api#javascript#uploads#directories#frontend
A

Alex Morgan

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-12T03:57:07.677Z