File System Api

Enable powerful file handling directly in the browser with the File System API. Read, write, and manage files with user permission controls and the high-performance Origin Private File System.

The File System API represents a significant advancement in web capabilities, enabling browsers to interact directly with files on a user's local device. This powerful API, extended by the File System Access API, opens doors for sophisticated web applications that rival native software in functionality. From code editors that work with local files to image editors with direct disk access, the File System API transforms what web applications can achieve.

For modern web development with Next.js and similar frameworks, the File System API enables scenarios previously impossible in browser-based applications. Consider the challenge of building a productivity suite where users expect to open, edit, and save documents without constant server roundtrips. The File System API makes this native-like experience achievable entirely within the browser, while maintaining the security model that keeps users safe.

Like other modern browser APIs such as the WebUSB API, the File System API provides powerful device access capabilities while maintaining strict security boundaries through user permission controls. Understanding the File System API requires grasping its dual nature. The API encompasses two distinct file systems with different characteristics and use cases. The user-visible file system represents the actual files and folders that users see when opening their file explorer. The origin private file system (OPFS) exists as a separate, virtual file system private to each web origin that offers superior performance through in-place file modifications.

Core Concepts and Architecture

File System Handles

At the heart of the File System API lies the concept of handles. A handle represents a reference to a file or directory in the file system, serving as your application's gateway to file operations. The FileSystemHandle interface forms the base type from which all file and directory handles derive. Understanding handles is essential because they encapsulate both the identity of a file and the permissions your application has for interacting with it.

Handles persist in your application even after the user closes the page, though users can revoke permissions at any time. This persistence model allows applications to remember which files a user was working on, enabling seamless workflows across sessions. When a user returns to your application, you can present the previously used files without requiring them to locate and reselect the same files again.

Obtaining Handles Through Pickers

The File System API provides two primary methods for obtaining file and directory handles, each presenting a native picker dialog:

showOpenFilePicker() opens a file selection dialog that allows users to choose one or more files from their device. This method returns an array of FileSystemFileHandle objects representing the selected files. You can configure the picker to filter by file type and allow or disallow multiple selections.

showDirectoryPicker() opens a directory selection dialog that allows users to choose a folder. This method returns a FileSystemDirectoryHandle that you can use to enumerate and access files within the selected directory. This capability proves essential for applications that need to process multiple files, such as batch image processors or file organization tools.

// Open a file picker for a single file
async function openFilePicker() {
 const [handle] = await window.showOpenFilePicker({
 types: [{
 description: 'Text Files',
 accept: { 'text/plain': ['.txt', '.md', '.json'] }
 }],
 multiple: false
 });
 return handle;
}

// Open a directory picker
async function openDirectoryPicker() {
 const handle = await window.showDirectoryPicker();
 return handle;
}

// Iterate through directory contents
async function listDirectoryContents(dirHandle) {
 for await (const entry of dirHandle.values()) {
 console.log(`${entry.kind}: ${entry.name}`);
 }
}

Reading and Writing Files

Opening Files for Reading

Reading file contents begins with obtaining a FileSystemFileHandle through the showOpenFilePicker() method. This method presents the browser's native file picker, allowing users to navigate their file system and select the specific file they want your application to access. The picker supports various configuration options, including whether to allow multiple file selection and which file types to accept as valid options.

Once you have a file handle, retrieving the actual File object requires calling the getFile() method. This method returns a Promise that resolves to a File object containing the file's contents and metadata. The File object works with standard web APIs, meaning you can pass it directly to FileReader, use it in fetch requests, or process it with other file-handling code you already have.

Writing and Saving Files

Writing files requires obtaining a writable handle, which you can do through showSaveFilePicker() for new files or by requesting write permission on an existing file handle. The actual writing operation uses FileSystemWritableFileStream, obtained by calling createWritable() on your file handle. This stream supports standard write() operations, writing data to a temporary file before committing changes to the actual file location.

// Complete file reading with error handling
async function readFileWithErrorHandling() {
 try {
 const [handle] = await window.showOpenFilePicker({
 types: [{
 description: 'Text Files',
 accept: { 'text/plain': ['.txt', '.md', '.json', '.csv'] }
 }]
 });

 const file = await handle.getFile();
 const contents = await file.text();

 return contents;
 } catch (error) {
 if (error.name === 'AbortError') {
 console.log('File selection was cancelled');
 return null;
 }
 throw error;
 }
}

// Complete file writing with error handling
async function writeFileWithErrorHandling(content, options = {}) {
 try {
 const handle = await window.showSaveFilePicker({
 suggestedName: options.suggestedName || 'document.txt',
 types: [{
 description: 'Text Files',
 accept: { 'text/plain': ['.txt'] }
 }]
 });

 const writable = await handle.createWritable();
 await writable.write(content);
 await writable.close();

 return handle;
 } catch (error) {
 if (error.name === 'AbortError') {
 console.log('Save operation was cancelled');
 return null;
 }
 throw error;
 }
}
Complete File Operations Example
1async function openAndReadFile() {2 // Request file selection from user3 const [handle] = await window.showOpenFilePicker({4 types: [{5 description: 'Text Files',6 accept: { 'text/plain': ['.txt', '.md', '.json'] }7 }],8 multiple: false9 });10 11 // Get the file object12 const file = await handle.getFile();13 14 // Read file contents15 const contents = await file.text();16 17 return contents;18}19 20async function saveFile(content, suggestedName = 'output.txt') {21 const handle = await window.showSaveFilePicker({22 suggestedName,23 types: [{24 description: 'Text Files',25 accept: { 'text/plain': ['.txt'] }26 }]27 });28 29 const writable = await handle.createWritable();30 await writable.write(content);31 await writable.close();32}

Origin Private File System

Understanding OPFS

The origin private file system represents a virtual file system that's private to each web origin and invisible to users. Unlike the user-visible file system where operations involve actual files in user-controlled directories, OPFS exists entirely within the browser's storage implementation. This architectural difference provides significant performance advantages while maintaining the file-based programming model developers recognize in modern web applications.

Accessing OPFS requires calling navigator.storage.getDirectory(), which returns a FileSystemDirectoryHandle for your origin's private directory. This directory serves as the root of your application's OPFS namespace, and you can create files and subdirectories within it using familiar methods like getFileHandle() and getDirectoryHandle().

Performance Advantages

OPFS delivers substantially better performance than the user-visible file system for several architectural reasons. The most significant difference involves write operations. When writing to the user-visible file system, browsers must create temporary files, perform security scans, and handle potential conflicts with other applications accessing the same files. OPFS writes occur in-place, meaning changes modify the file contents directly without temporary file overhead.

OPFS also supports synchronous file operations through FileSystemSyncAccessHandle, available within Web Workers. This synchronous interface eliminates the overhead of Promise resolution and event loop scheduling for critical file operations.

OPFS Code Example

// Access OPFS for high-performance local storage
async function setupOPFS() {
 // Get the origin's private directory
 const opfsRoot = await navigator.storage.getDirectory();

 // Create or open a file for application data
 const fileHandle = await opfsRoot.getFileHandle('app-data.json', {
 create: true
 });

 // Write data to the file
 const writable = await fileHandle.createWritable();
 await writable.write(JSON.stringify({ 
 lastUpdated: new Date().toISOString(),
 settings: { theme: 'dark', language: 'en' }
 }));
 await writable.close();

 // Read the data back
 const file = await fileHandle.getFile();
 const data = JSON.parse(await file.text());

 return { opfsRoot, fileHandle, data };
}

// Create subdirectories for organization
async function createStructuredStorage() {
 const root = await navigator.storage.getDirectory();
 
 // Create subdirectories
 const cacheDir = await root.getDirectoryHandle('cache', { create: true });
 const dataDir = await root.getDirectoryHandle('data', { create: true });
 
 // Store files in appropriate directories
 const cacheFile = await cacheDir.getFileHandle('temporary.dat', { create: true });
 const dataFile = await dataDir.getFileHandle('persistent.json', { create: true });
 
 return { root, cacheDir, dataDir, cacheFile, dataFile };
}

Security and Permissions

Permission Model

The File System API implements a robust permission model that puts users in control of their files. No file access occurs without explicit user consent, and users must take affirmative action through picker dialogs to grant access. This design prevents applications from silently scanning file systems or accessing files users haven't intentionally shared with the application.

Permission requests occur at the handle level through implicit grants (via picker selection) and explicit grants (via requestPermission()). Permissions can be temporary (session-scoped) or persistent (surviving browser restarts). Applications should request persistent permissions only when genuinely needed and provide clear value to justify the ongoing access.

Handling Permission Denials

Applications must handle permission denials gracefully, as users may revoke access at any time. When a user denies file access entirely, your application should guide them through the process of reselecting files rather than failing silently or presenting confusing error messages. Design your application to function with read-only access when write permission is denied, providing appropriate feedback to users about limited functionality.

// Check and request permissions
async function checkAndRequestPermission(handle, readOnly = true) {
 const options = { mode: readOnly ? 'read' : 'readwrite' };
 
 // Check current permission state
 const status = await handle.queryPermission(options);
 
 if (status === 'granted') {
 return true;
 }
 
 if (status === 'prompt') {
 // Request permission
 const requestStatus = await handle.requestPermission(options);
 return requestStatus === 'granted';
 }
 
 // Status is 'denied'
 return false;
}

// Graceful degradation when permissions are denied
async function readFileWithGracefulDegradation() {
 try {
 return await readFileWithErrorHandling();
 } catch (error) {
 if (error.name === 'NotAllowedError') {
 // Offer alternative: use file upload instead
 return await uploadAndReadFile();
 }
 throw error;
 }
}

Security Considerations

Files accessed through the API receive a "mark of the web" that identifies them as originating from internet sources. Operating systems may display warnings before executing such files, preventing malicious code from executing through downloaded files. Browser vendors implement additional protections including Safe Browsing checks for files downloaded or created through web applications.

Browser Compatibility

86+

Chrome/Edge Version

111+

Firefox Version

15.2+

Safari Version

Best Practices

Error Handling

Robust error handling is essential when working with file operations, as numerous failure modes exist that your application must handle gracefully. Permission errors occur when users deny access or revoke previously granted permissions. Handle these errors by providing clear feedback and guiding users through re-granting access if desired.

Quota errors occur when the origin's storage quota is exhausted. Monitor storage usage through navigator.storage.estimate() and alert users before operations fail due to space limitations. Wrap file operations in try-catch blocks and provide meaningful error messages that help users understand what went wrong.

Performance Optimization

Choose the appropriate file system for your use case to optimize performance. Use the origin private file system for frequent read/write operations, caching, and internal application data. The user-visible file system better suits workflows where users explicitly save and load files as part of their interaction model.

For large files, implement streaming reads and writes rather than loading entire files into memory. The File API supports reading files in chunks through slice() operations, and writable streams accept data progressively. Consider using Web Workers for intensive file operations to avoid blocking the main thread.

User Experience

Design file operations around user expectations and workflows. When presenting file pickers, provide sensible defaults and filtering to help users find appropriate files quickly. Remember what files users have previously accessed and offer convenient re-selection options to reduce friction in repeated workflows.

Provide clear feedback during file operations, especially for large files that take noticeable time to read or write. Progress indicators and estimated time remaining help users understand that the application is working. Consider offline scenarios where the user-visible file system might be unavailable but OPFS remains functional.

Key File System API Capabilities

Everything you need to build powerful file-handling web applications

Direct File Access

Open, read, and write files on the user's device with explicit permission grants

Directory Management

Enumerate directories and work with file hierarchies through intuitive APIs

Origin Private Storage

High-performance virtual file system private to each origin with in-place writes

Persistent Handles

Remember user's file selections across sessions for seamless workflows

Streaming I/O

Process large files efficiently with streaming reads and writes

Web Worker Support

Offload file operations to workers with synchronous access for maximum performance

Frequently Asked Questions

Build Powerful File-Handling Web Applications

Leverage the File System API to create native-like experiences for document editing, media processing, and local data management.