File System Access API: Reading and Writing Local Files from WordPress Admin

The File System Access API lets browser-side JavaScript read and write files on the user’s local filesystem with their explicit permission. In WordPress admin tools or media helpers this enables drag-to-edit workflows — for example, editing a CSS file directly from the browser without an FTP client.

Problem: A WordPress admin tool needs to read or write files directly on the user's local machine — for example, importing a large CSV or exporting a configuration file — without requiring a server upload or download.

Solution: Use the File System Access API — call window.showOpenFilePicker() to let the user select a file, read it with a FileSystemFileHandle, and write changes back with createWritable(). All file I/O happens locally in the browser without any server round-trip.

The examples below open a file picker for a single file, read and display its content in a textarea, then write the modified content back to the same file — all without a server round-trip.

// file-editor.js — enqueued on a custom WP admin page
let fileHandle = null;

// 1. Open a file picker (requires a user gesture — attach to a button click)
document.getElementById( 'btn-open' ).addEventListener( 'click', async () => {
    try {
        [ fileHandle ] = await window.showOpenFilePicker( {
            types: [
                {
                    description: 'CSS / JS files',
                    accept: { 'text/css': ['.css'], 'text/javascript': ['.js'] },
                },
            ],
            excludeAcceptAllOption: false,
            multiple: false,
        } );

        const file    = await fileHandle.getFile();
        const content = await file.text();

        document.getElementById( 'editor-textarea' ).value = content;
        document.getElementById( 'file-name' ).textContent = file.name;
        document.getElementById( 'btn-save' ).disabled = false;
    } catch ( err ) {
        if ( err.name !== 'AbortError' ) console.error( err );
    }
} );

// 2. Save the edited content back to the original file
document.getElementById( 'btn-save' ).addEventListener( 'click', async () => {
    if ( ! fileHandle ) return;

    // Request write permission (browser will prompt if not already granted)
    const perm = await fileHandle.requestPermission( { mode: 'readwrite' } );
    if ( perm !== 'granted' ) return;

    const writable = await fileHandle.createWritable();
    await writable.write( document.getElementById( 'editor-textarea' ).value );
    await writable.close();

    showNotice( 'File saved successfully.' );
} );

Handle directory access for batch operations — useful for reading an entire theme folder:

// Open an entire directory and list all CSS files
document.getElementById( 'btn-open-dir' ).addEventListener( 'click', async () => {
    const dirHandle = await window.showDirectoryPicker();
    const cssFiles  = [];

    for await ( const [name, handle] of dirHandle ) {
        if ( handle.kind === 'file' && name.endsWith( '.css' ) ) {
            cssFiles.push( { name, handle } );
        }
    }

    // Render a file list
    const list = document.getElementById( 'file-list' );
    list.innerHTML = '';
    for ( const { name, handle } of cssFiles ) {
        const li = document.createElement( 'li' );
        li.textContent = name;
        li.addEventListener( 'click', async () => {
            const file    = await handle.getFile();
            const content = await file.text();
            document.getElementById( 'editor-textarea' ).value = content;
            fileHandle = handle;
        } );
        list.appendChild( li );
    }
} );

NOTE: The File System Access API is supported in Chrome and Edge but not in Firefox or Safari. Always check typeof window.showOpenFilePicker === 'function' before using it and provide a server-side fallback upload form for unsupported browsers.

Leave Comment

Your email address will not be published. Required fields are marked *