Last updated: February 5, 2025

<span aria-hidden="true" id="persistent-file-handling-with-the-file-system-access-api"></span>

# Persistent file handling with the File System Access API

![Kevin van Zonneveld](/assets/images/teammates/avatar-kvz-4.jpg?dpl=dpl_C6YH6XrtnwbHLJcm1LDKywQn4CB3)

#### Kevin van Zonneveld

Co-founder · Amsterdam, The Netherlands · Show bio

[](https://x.com/kvz)[](https://github.com/kvz)

Traditional file upload mechanisms have long been constrained by the browser's sandbox. The typical`<input type="file">` approach forces you to re-select files after a page refresh and does not expose direct file system paths. The File System Access API offers a modern solution by enabling persistent file handles that support persistent file uploads and direct file system integration. This means you may resume interrupted uploads without requiring users to reselect their files. Because browser support varies, it is crucial to implement progressive enhancement via fallback libraries.

<span aria-hidden="true" id="browser-support-and-progressive-enhancement"></span>

## Browser support and progressive enhancement

For optimal compatibility, use the browser-fs-access library, which provides a reliable fallback for browsers that do not support the native File System Access API. Currently, the API is supported in Chromium-based browsers (e.g., Chrome and Edge) from version 86 onward, while Brave offers experimental support behind a flag. In contrast, stable releases of Safari and Firefox do not support this API. By checking for native support, you can seamlessly switch between the API and the fallback.

```javascript
import { fileOpen, fileSave, supported } from 'browser-fs-access'

async function handleFileAccess() {
  if (supported) {
    console.log('Using native File System Access API')
  } else {
    console.log('Using legacy fallback via browser-fs-access')
  }

  try {
    const blob = await fileOpen({
      mimeTypes: ['image/*'],
      multiple: false,
    })
    return blob
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('User cancelled file selection')
    } else {
      console.error('Error accessing file:', err)
    }
    return null
  }
}

```

<span aria-hidden="true" id="security-considerations"></span>

## Security considerations

The File System Access API enforces several security measures to protect user data:

* Permissions are origin-bound and automatically revoked when all tabs for the origin close.
* A secure context (HTTPS) is mandatory for production use.
* Write operations request explicit user consent before modifying files.
* Access remains restricted to specific system folders and does not allow cross-origin file access.

Implement robust permission checks as shown below. If write access is required, use`{ mode: 'readwrite' }` instead of `{ mode: 'read' }`.

```javascript
async function verifyPermissions(handle) {
  const options = { mode: 'read' }

  try {
    if ((await handle.queryPermission(options)) === 'granted') {
      return true
    }

    const permission = await handle.requestPermission(options)
    return permission === 'granted'
  } catch (err) {
    console.error('Permission error:', err)
    return false
  }
}

```

<span aria-hidden="true" id="directory-handling"></span>

## Directory handling

Enabling directory uploads allows you to process entire file hierarchies. The following example recursively collects files from a user-selected directory:

```javascript
async function handleDirectoryUpload() {
  try {
    const dirHandle = await window.showDirectoryPicker()
    const files = []

    async function* getFilesRecursively(entry) {
      for await (const handle of entry.values()) {
        if (handle.kind === 'file') {
          const file = await handle.getFile()
          if (file) yield file
        } else if (handle.kind === 'directory') {
          yield* getFilesRecursively(handle)
        }
      }
    }

    for await (const file of getFilesRecursively(dirHandle)) {
      files.push(file)
    }

    return files
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('User cancelled directory selection')
    } else if (err.name === 'SecurityError') {
      console.error('Permission denied')
    } else {
      console.error('Error accessing directory:', err)
    }
    return []
  }
}

```

<span aria-hidden="true" id="drag-and-drop-integration"></span>

## Drag and drop integration

Integrate drag and drop to improve the file selection experience. The example below demonstrates type checking for dropped items and processes both file handles and standard file objects:

```javascript
function setupDragAndDrop(dropZone) {
  dropZone.addEventListener('dragover', (e) => {
    e.preventDefault()
    e.dataTransfer.dropEffect = 'copy'
    dropZone.classList.add('drag-active')
  })

  dropZone.addEventListener('dragleave', () => {
    dropZone.classList.remove('drag-active')
  })

  dropZone.addEventListener('drop', async (e) => {
    e.preventDefault()
    dropZone.classList.remove('drag-active')

    const items = Array.from(e.dataTransfer.items).filter((item) => item.kind === 'file')

    try {
      const handles = await Promise.all(
        items.map((item) => item.getAsFileSystemHandle?.() ?? item.getAsFile()),
      )

      for (const handle of handles) {
        if (!handle) continue

        if (handle instanceof File) {
          // Process a regular file dropped
          console.log('Processing dropped file:', handle.name)
        } else if (handle.kind === 'file') {
          const file = await handle.getFile()
          console.log('Processing file handle:', file.name)
        } else if (handle.kind === 'directory') {
          console.log('Processing directory:', handle.name)
        }
      }
    } catch (err) {
      console.error('Error processing dropped items:', err)
    }
  })
}

```

<span aria-hidden="true" id="large-file-handling"></span>

## Large file handling

When working with large files, streaming data from the file minimizes memory consumption. The following example processes a file in manageable chunks while reporting progress:

```javascript
async function handleLargeFile(fileHandle) {
  try {
    const file = await fileHandle.getFile()
    const stream = file.stream()
    const reader = stream.getReader()

    const chunks = []
    let totalSize = 0

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      chunks.push(value)
      totalSize += value.length

      // Report progress
      const progress = (totalSize / file.size) * 100
      console.log(`Processing: ${progress.toFixed(2)}%`)
    }

    return new Blob(chunks, { type: file.type })
  } catch (err) {
    console.error('Error processing file:', err)
    throw err
  }
}

```

<span aria-hidden="true" id="error-handling-strategies"></span>

## Error handling strategies

Ensure your file operations are robust with comprehensive error handling. The strategy below checks permissions, validates file access, and addresses common error cases:

```javascript
async function safeFileOperation(handle) {
  if (!handle) {
    throw new Error('No file handle provided')
  }

  try {
    const permissionGranted = await verifyPermissions(handle)
    if (!permissionGranted) {
      throw new Error('Permission denied')
    }

    const file = await handle.getFile()
    if (!file) {
      throw new Error('Could not access file')
    }

    return file
  } catch (err) {
    switch (err.name) {
      case 'NotFoundError':
        console.error('File no longer exists')
        break
      case 'SecurityError':
        console.error('Permission denied')
        break
      case 'NotAllowedError':
        console.error('User denied permission')
        break
      default:
        console.error('Unexpected error:', err)
    }
    throw err
  }
}

```

The File System Access API transforms web-based file handling with direct integration into the local file system. Although browser support is limited to certain environments, implementing progressive enhancement ensures that your users have a reliable experience across platforms.

For advanced file upload implementations, consider exploring tools like [Uppy⁠](https://uppy.io) and[Tus⁠](https://tus.io) to enable resilient, resumable uploads.

\#file-upload-sites#file-system-access-api#client-side-file-handling#handling-uploads-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
