showOpenFilePicker()
Opens the system's file picker dialog for selecting existing files to read. Returns an array of FileSystemFileHandle objects.
showSaveFilePicker()
Opens the system's save dialog for creating new files or overwriting existing ones. Returns a single FileSystemFileHandle.
showDirectoryPicker()
Opens the system directory picker for selecting or creating folder access. Returns a FileSystemDirectoryHandle.
1// Core picker methods2 3// Open file(s) for reading4const [handle] = await window.showOpenFilePicker({5 types: [6 {7 description: 'Text files',8 accept: { 'text/plain': ['.txt', '.md', '.json'] }9 }10 ],11 multiple: false12});13 14// Save file to disk15const saveHandle = await window.showSaveFilePicker({16 suggestedName: 'document.txt',17 types: [18 {19 description: 'Text document',20 accept: { 'text/plain': ['.txt'] }21 }22 ]23});24 25// Select directory for batch operations26const dirHandle = await window.showDirectoryPicker();27 28// Read file contents from handle29const file = await handle.getFile();30const contents = await file.text();31 32// Write to file via writable stream33const writable = await saveHandle.createWritable();34await writable.write('Hello, File System!');35await writable.close();1// Reading files with getFile()2 3async function readTextFile(handle) {4 const file = await handle.getFile();5 return await file.text();6}7 8async function readJsonFile(handle) {9 const file = await handle.getFile();10 const text = await file.text();11 return JSON.parse(text);12}13 14async function readLargeFile(handle) {15 const file = await handle.getFile();16 const stream = file.stream();17 const reader = stream.getReader();18 19 while (true) {20 const { done, value } = await reader.read();21 if (done) break;22 // Process chunk (value is Uint8Array)23 console.log('Received chunk:', value.length, 'bytes');24 }25}26 27// Handle errors gracefully28async function safeReadFile(handle) {29 try {30 const file = await handle.getFile();31 return await file.text();32 } catch (error) {33 if (error.name === 'NotFoundError') {34 throw new Error('File has been deleted or moved');35 }36 throw error;37 }38}1// Writing files with createWritable()2 3async function writeTextFile(handle, content) {4 const writable = await handle.createWritable();5 await writable.write(content);6 await writable.close();7}8 9async function writeJsonFile(handle, data) {10 const writable = await handle.createWritable();11 await writable.write(JSON.stringify(data, null, 2));12 await writable.close();13}14 15async function appendToFile(handle, content) {16 const writable = await handle.createWritable({ keepExistingData: true });17 await writable.seek(0); // Move to end of existing data18 await writable.write(content);19 await writable.close();20}21 22// Write from a blob or buffer23async function writeBuffer(handle, buffer) {24 const writable = await handle.createWritable();25 await writable.write(buffer);26 await writable.close();27}28 29// Using write options for truncate or append30async function writeWithOptions(handle, content, mode = 'truncate') {31 const writable = await handle.createWritable({32 keepExistingData: mode === 'append'33 });34 35 if (mode === 'truncate') {36 await writable.write({ size: 0 }); // Clear file37 }38 39 await writable.write(content);40 await writable.close();41}1// Directory operations2 3// List all items in a directory4async function listDirectory(dirHandle) {5 const items = [];6 for await (const [name, handle] of dirHandle.entries()) {7 items.push({8 name,9 kind: handle.kind, // 'file' or 'directory'10 handle11 });12 }13 return items;14}15 16// Get or create nested handles17async function getOrCreateFile(dirHandle, filename) {18 return dirHandle.getFileHandle(filename, { create: true });19}20 21async function getOrCreateDir(dirHandle, dirname) {22 return dirHandle.getDirectoryHandle(dirname, { create: true });23}24 25// Remove a file or directory26async function removeItem(dirHandle, name) {27 await dirHandle.removeEntry(name, { recursive: true });28}29 30// Recursively walk directory tree31async function* walkDirectory(dirHandle, path = '') {32 for await (const [name, handle] of dirHandle.entries()) {33 const fullPath = path ? `${path}/${name}` : name;34 if (handle.kind === 'directory') {35 yield { path: fullPath, kind: 'directory', handle };36 yield* walkDirectory(handle, fullPath);37 } else {38 yield { path: fullPath, kind: 'file', handle };39 }40 }41}42 43// Count files in directory tree44async function countFiles(dirHandle) {45 let count = 0;46 for await (const item of walkDirectory(dirHandle)) {47 if (item.kind === 'file') count++;48 }49 return count;50}1// Permission management2 3async function requestFilePermission(handle, readOnly = true) {4 const options = { mode: readOnly ? 'read' : 'readwrite' };5 6 // Check current permission state7 const status = await handle.queryPermission(options);8 9 if (status === 'granted') {10 return true;11 }12 13 if (status === 'denied') {14 return false;15 }16 17 // Request permission from user18 try {19 const result = await handle.requestPermission(options);20 return result === 'granted';21 } catch (error) {22 console.error('Permission request failed:', error);23 return false;24 }25}26 27// Wrapper for file operations with auto-permission28async function withFilePermission(handle, operation, readOnly = true) {29 const hasPermission = await requestFilePermission(handle, readOnly);30 if (!hasPermission) {31 throw new Error('File permission denied');32 }33 return operation(handle);34}35 36// Check permission without prompting37async function hasAccess(handle, mode = 'read') {38 try {39 const status = await handle.queryPermission({ mode });40 return status === 'granted';41 } catch {42 return false;43 }44}45 46// Persist handle and request persistent access47async function openWithPersistence() {48 const [handle] = await window.showOpenFilePicker();49 50 // Request persistent permission51 const opts = { mode: 'readwrite', keepPermission: true };52 if ((await handle.queryPermission(opts)) === 'prompt') {53 await handle.requestPermission(opts);54 }55 56 return handle;57}1// Browser compatibility helpers2 3// Check if File System Access API is supported4function isFileSystemAccessSupported() {5 return 'showOpenFilePicker' in window;6}7 8// Feature detection with graceful fallback9async function openFileWithFallback() {10 if (isFileSystemAccessSupported()) {11 const [handle] = await window.showOpenFilePicker();12 const file = await handle.getFile();13 return { handle, file, method: 'fs-access' };14 }15 16 // Fallback to traditional file input17 return new Promise((resolve) => {18 const input = document.createElement('input');19 input.type = 'file';20 input.accept = '.txt,.md,.json';21 22 input.onchange = (event) => {23 const file = event.target.files[0];24 resolve({ file, method: 'file-input' });25 };26 27 input.click();28 });29}30 31// Detect specific capabilities32async function getBrowserCapabilities() {33 const caps = {34 picker: 'showOpenFilePicker' in window,35 directory: 'showDirectoryPicker' in window,36 writable: false,37 syncHandle: false38 };39 40 if (caps.picker) {41 try {42 const testHandle = await window.showSaveFilePicker();43 caps.writable = true;44 testHandle.handle.cancel();45 } catch (e) {46 // Feature exists but failed to use47 }48 }49 50 return caps;51}52 53// UI message based on support54function getUnsupportedMessage() {55 const ua = navigator.userAgent;56 57 if (ua.includes('Firefox')) {58 return 'Firefox: Enable dom.webshell.enabled and dom.fileHandle.enabled in about:config';59 }60 if (ua.includes('Safari') && !ua.includes('Chrome')) {61 return 'Safari: Enable Experimental Features > File System Access API in Develop menu';62 }63 return 'Your browser does not support the File System Access API. Try Chrome, Edge, or Opera.';64}1// Complete Document Editor using File System Access API2 3class DocumentEditor {4 constructor() {5 this.handle = null;6 this.filename = 'Untitled';7 this.content = '';8 this.unsavedChanges = false;9 }10 11 // Check for API support12 static isSupported() {13 return 'showOpenFilePicker' in window;14 }15 16 // Open existing file17 async open() {18 try {19 const [handle] = await window.showOpenFilePicker({20 types: [21 {22 description: 'Text Documents',23 accept: {24 'text/plain': ['.txt', '.md', '.json', '.html', '.css', '.js']25 }26 }27 ]28 });29 30 return await this.loadFromHandle(handle);31 } catch (error) {32 if (error.name !== 'AbortError') {33 console.error('Failed to open file:', error);34 }35 return false;36 }37 }38 39 // Load content from file handle40 async loadFromHandle(handle) {41 try {42 // Request read permission43 if ((await handle.queryPermission({ mode: 'read' })) === 'prompt') {44 await handle.requestPermission({ mode: 'read' });45 }46 47 const file = await handle.getFile();48 this.content = await file.text();49 this.filename = file.name;50 this.handle = handle;51 this.unsavedChanges = false;52 53 return true;54 } catch (error) {55 console.error('Failed to load file:', error);56 return false;57 }58 }59 60 // Create new file (shows save dialog)61 async create() {62 try {63 const handle = await window.showSaveFilePicker({64 suggestedName: `${this.filename}.txt`,65 types: [66 {67 description: 'Text Document',68 accept: { 'text/plain': ['.txt'] }69 },70 {71 description: 'Markdown',72 accept: { 'text/markdown': ['.md'] }73 }74 ]75 });76 77 this.content = '';78 this.filename = handle.name;79 this.handle = handle;80 this.unsavedChanges = false;81 82 return true;83 } catch (error) {84 if (error.name !== 'AbortError') {85 console.error('Failed to create file:', error);86 }87 return false;88 }89 }90 91 // Save current file92 async save() {93 if (!this.handle) {94 return await this.saveAs();95 }96 97 try {98 // Request write permission if needed99 if ((await this.handle.queryPermission({ mode: 'readwrite' })) === 'prompt') {100 await this.handle.requestPermission({ mode: 'readwrite' });101 }102 103 const writable = await this.handle.createWritable();104 await writable.write(this.content);105 await writable.close();106 107 this.unsavedChanges = false;108 return true;109 } catch (error) {110 console.error('Failed to save:', error);111 return false;112 }113 }114 115 // Save as new file116 async saveAs() {117 try {118 const handle = await window.showSaveFilePicker({119 suggestedName: this.filename,120 types: [121 {122 description: 'Text Document',123 accept: { 'text/plain': ['.txt'] }124 }125 ]126 });127 128 this.handle = handle;129 this.filename = handle.name;130 131 return await this.save();132 } catch (error) {133 if (error.name !== 'AbortError') {134 console.error('Failed to save as:', error);135 }136 return false;137 }138 }139 140 // Update content141 updateContent(newContent) {142 this.content = newContent;143 this.unsavedChanges = true;144 }145 146 // Get current content147 getContent() {148 return this.content;149 }150 151 // Check for unsaved changes152 hasUnsavedChanges() {153 return this.unsavedChanges;154 }155 156 // Get filename157 getFilename() {158 return this.filename;159 }160}161 162// Usage example163const editor = new DocumentEditor();164 165// Hook up to UI166document.getElementById('open-btn').onclick = () => editor.open();167document.getElementById('save-btn').onclick = () => editor.save();168document.getElementById('new-btn').onclick = () => editor.create();169 170// Handle content changes171document.getElementById('editor').oninput = (e) => {172 editor.updateContent(e.target.value);173};1// Origin Private File System (OPFS) for performance2 3class OPFSManager {4 constructor() {5 this.root = null;6 }7 8 // Initialize OPFS access9 async init() {10 this.root = await navigator.storage.getDirectory();11 return this;12 }13 14 // Get or create a file in OPFS15 async getFileHandle(name, options = {}) {16 if (!this.root) await this.init();17 return this.root.getFileHandle(name, options);18 }19 20 // Get or create a directory in OPFS21 async getDirectoryHandle(name, options = {}) {22 if (!this.root) await this.init();23 return this.root.getDirectoryHandle(name, options);24 }25 26 // Write data to OPFS file27 async writeToOPFS(filename, content) {28 const handle = await this.getFileHandle(filename, { create: true });29 const writable = await handle.createWritable();30 await writable.write(content);31 await writable.close();32 }33 34 // Read data from OPFS file35 async readFromOPFS(filename) {36 const handle = await this.getFileHandle(filename);37 const file = await handle.getFile();38 return await file.text();39 }40 41 // List all files in OPFS root42 async listFiles() {43 if (!this.root) await this.init();44 const files = [];45 for await (const [name, handle] of this.root.entries()) {46 files.push({ name, kind: handle.kind });47 }48 return files;49 }50 51 // Delete file from OPFS52 async deleteFromOPFS(filename) {53 if (!this.root) await this.init();54 await this.root.removeEntry(filename);55 }56}57 58// Performance optimization example59async function processLargeFileOptimized(userFileHandle) {60 const opfs = new OPFSManager();61 await opfs.init();62 63 // Read user file64 const userFile = await userFileHandle.getFile();65 const chunks = [];66 67 // Process in chunks using OPFS for intermediate storage68 const stream = userFile.stream();69 const reader = stream.getReader();70 let chunkIndex = 0;71 72 while (true) {73 const { done, value } = await reader.read();74 if (done) break;75 76 // Store chunk in OPFS77 await opfs.writeToOPFS(`chunk-${chunkIndex}.tmp`, value);78 chunks.push(`chunk-${chunkIndex}.tmp`);79 chunkIndex++;80 }81 82 // Process each chunk from OPFS83 const processedChunks = [];84 for (const chunkName of chunks) {85 const data = await opfs.readFromOPFS(chunkName);86 const processed = await transformData(data); // Your processing function87 processedChunks.push(processed);88 89 // Clean up temp file90 await opfs.deleteFromOPFS(chunkName);91 }92 93 // Combine results94 return processedChunks.join('');95}1// Best practices for File System Access API2 3class RobustFileHandler {4 constructor() {5 this.currentHandle = null;6 }7 8 // 1. Always use try-catch with specific error handling9 async safeOpen() {10 try {11 const [handle] = await window.showOpenFilePicker();12 return handle;13 } catch (error) {14 switch (error.name) {15 case 'AbortError':16 console.log('User cancelled the file picker');17 return null;18 case 'SecurityError':19 throw new Error('File access blocked by security policy');20 default:21 console.error('Unknown error:', error);22 throw error;23 }24 }25 }26 27 // 2. Request permissions before critical operations28 async ensurePermission(handle, readOnly = true) {29 const options = { mode: readOnly ? 'read' : 'readwrite' };30 31 try {32 const status = await handle.queryPermission(options);33 if (status === 'granted') return true;34 if (status === 'denied') return false;35 36 const result = await handle.requestPermission(options);37 return result === 'granted';38 } catch (error) {39 console.error('Permission error:', error);40 return false;41 }42 }43 44 // 3. Check file availability before operations45 async isFileAvailable(handle) {46 try {47 const file = await handle.getFile();48 // If we can get the file, it's available49 return true;50 } catch (error) {51 if (error.name === 'NotFoundError') return false;52 throw error; // Other errors should propagate53 }54 }55 56 // 4. Handle concurrent modifications57 async saveWithConflictCheck(handle, newContent) {58 // Get current file modification time59 const file = await handle.getFile();60 const lastModified = file.lastModified;61 62 // Check if file has been modified since we opened it63 if (this.lastKnownModified && lastModified !== this.lastKnownModified) {64 const userWantsMerge = confirm(65 'The file has been modified elsewhere. Overwrite anyway?'66 );67 if (!userWantsMerge) return false;68 }69 70 // Proceed with save71 const writable = await handle.createWritable();72 await writable.write(newContent);73 await writable.close();74 75 // Update our tracking76 this.lastKnownModified = (await handle.getFile()).lastModified;77 return true;78 }79 80 // 5. Provide undo capability with OPFS81 async createSnapshot(content, operation) {82 const opfs = await navigator.storage.getDirectory();83 const snapshotName = `snapshot-${Date.now()}-${operation}.txt`;84 85 const handle = await opfs.getFileHandle(snapshotName, { create: true });86 const writable = await handle.createWritable();87 await writable.write(content);88 await writable.close();89 90 return snapshotName;91 }92}93 94// 6. Use AbortController for cancellable operations95class CancellableFileReader {96 constructor() {97 this.abortController = new AbortController();98 }99 100 async readLargeFile(handle) {101 try {102 const file = await handle.getFile();103 const stream = file.stream();104 const reader = stream.getReader();105 106 while (true) {107 const { done, value } = await reader.read({108 signal: this.abortController.signal109 });110 if (done) break;111 // Process value...112 }113 } catch (error) {114 if (error.name === 'AbortError') {115 console.log('Read operation was cancelled');116 return;117 }118 throw error;119 }120 }121 122 cancel() {123 this.abortController.abort();124 }125}Sources
- WICG File System Access Specification - Official specification from the Web Platform Incubator Community Group
- Chrome Developer Documentation - File System Access API - Google's official implementation guide
- MDN Web Docs - File System API - Mozilla's comprehensive documentation
- Can I Use File System Access API - Browser compatibility data