loading
Warning:
Chromium only, for now Firefox and Safari implement neither picker. Check IsSupported and keep a download-based fallback - FileReader for reading and ObjectUrls for handing a file back are the everywhere-else path.
Note:
Every picker needs a clickShowOpenFilePicker, ShowSaveFilePicker and ShowDirectoryPicker must be called from a user-gesture handler, and each returns null when the user cancels rather than throwing - dismissing a dialog is not an error.

Support check

IsSupported / IsDirectorySupported

The file pickers and the directory picker ship together in practice, but they are separate entry points, so both are checkable. During prerender/SSR both return false rather than throwing.

C#
@inject Bit.Butil.FileSystem fileSystem

var files = await fileSystem.IsSupported();
var folders = await fileSystem.IsDirectorySupported();
Live sample
support check output
Results will appear here when you interact with the samples.

Open and read

ShowOpenFilePicker / ReadText / GetFileInfo

The accept list is what turns the picker's filter dropdown from '*.*' into named groups. A group with no extensions is dropped rather than sent, because the underlying API rejects an empty filter outright. GetFileInfo reads name, size, type and last-modified without loading the contents.

C#
var picked = await fileSystem.ShowOpenFilePicker(
    multiple: false,
    accept:
    [
        new FilePickerType
        {
            Description = "Text files",
            MimeType = "text/plain",
            Extensions = [".txt", ".md", ".json"],
        },
    ]);

if (picked is { Length: > 0 })
{
    var info = await fileSystem.GetFileInfo(picked[0]);
    var text = await fileSystem.ReadText(picked[0]);
}
Live sample
open output
Results will appear here when you interact with the samples.

Save, and save again

ShowSaveFilePicker / WriteText

This is the part a download cannot do. ShowSaveFilePicker reserves a name but writes nothing; the handle it returns can then be written to as often as you like, so the second Save goes straight to the same file with no dialog. Nothing here needs a server round-trip.

C#
_target = await fileSystem.ShowSaveFilePicker(
    suggestedName: "notes.txt",
    accept: [new FilePickerType { Description = "Text", MimeType = "text/plain", Extensions = [".txt"] }]);

// as many times as you like, no further prompts:
await fileSystem.WriteText(_target!, _contents);
Live sample
save output
Results will appear here when you interact with the samples.

Walk a folder

ShowDirectoryPicker / ListDirectory / GetFile / Remove

A directory handle grants access to everything under it. ListDirectory returns the immediate children only - descend by calling it again on a child whose IsDirectory is true. GetFile fetches a named entry, optionally creating it, and takes a bare name: this API has no path traversal.

C#
var folder = await fileSystem.ShowDirectoryPicker(write: true);
if (folder is not null)
{
    foreach (var entry in await fileSystem.ListDirectory(folder))
    {
        // entry.IsDirectory to recurse, entry.Name to show
    }

    // create a file inside it and write to it:
    var created = await fileSystem.GetFile(folder, "hello.txt", create: true);
    await fileSystem.WriteText(created!, "written from Blazor");

    await fileSystem.Remove(folder, "hello.txt");
}
Live sample
folder output
Results will appear here when you interact with the samples.

Permissions

QueryPermission / RequestPermission / Release

A handle you persisted from a previous session comes back as Prompt rather than Granted - the grant does not survive a reload. RequestPermission, from a user gesture, is what makes it usable again. Release drops a handle you are finished with; handles are held on the JS side for as long as your app might use them.

C#
var state = await fileSystem.QueryPermission(handle, write: true);
if (state == FileSystemPermission.Prompt)
{
    // from a click handler:
    state = await fileSystem.RequestPermission(handle, write: true);
}

// when you're done with it:
await fileSystem.Release(handle);
Live sample
Handle none picked yet
permission output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes window.showOpenFilePicker. Returns default (false) during prerender/SSR instead of throwing.
IsDirectorySupported
ValueTask<bool> IsDirectorySupported()
True when the runtime exposes window.showDirectoryPicker.
ShowOpenFilePicker
ValueTask<FileSystemHandleInfo[]?> ShowOpenFilePicker(bool multiple = false, FilePickerType[]? accept = null, bool excludeAcceptAllOption = false, string startIn = &quot;&quot;)
Shows the open-file picker. Null when cancelled or unavailable. Requires a user gesture.
ShowSaveFilePicker
ValueTask<FileSystemHandleInfo?> ShowSaveFilePicker(string suggestedName = &quot;&quot;, FilePickerType[]? accept = null, bool excludeAcceptAllOption = false, string startIn = &quot;&quot;)
Shows the save-file picker. Reserves a name; nothing is written until you call a Write method.
ShowDirectoryPicker
ValueTask<FileSystemHandleInfo?> ShowDirectoryPicker(bool write = false, string startIn = &quot;&quot;)
Shows the directory picker, granting access to a whole folder.
ListDirectory
ValueTask<FileSystemHandleInfo[]> ListDirectory(FileSystemHandleInfo directory)
The directory's immediate children. Not recursive.
ReadText
ValueTask<string?> ReadText(FileSystemHandleInfo file)
Reads a file as text. Null when it is unreadable - moved, deleted, or permission revoked.
ReadBytes
ValueTask<byte[]?> ReadBytes(FileSystemHandleInfo file)
Reads a file as bytes.
GetFileInfo
ValueTask<FileSystemFileInfo?> GetFileInfo(FileSystemHandleInfo file)
Name, size, MIME type and last-modified, without reading the contents.
WriteText
ValueTask<bool> WriteText(FileSystemHandleInfo file, string text)
Overwrites a file with text. False when the write was refused.
WriteBytes
ValueTask<bool> WriteBytes(FileSystemHandleInfo file, byte[] data)
Overwrites a file with bytes.
GetFile
ValueTask<FileSystemHandleInfo?> GetFile(FileSystemHandleInfo directory, string name, bool create = false)
A named file inside a directory. Null when missing and create is false.
Remove
ValueTask<bool> Remove(FileSystemHandleInfo directory, string name, bool recursive = false)
Deletes an entry. False when missing or refused.
QueryPermission
ValueTask<FileSystemPermission> QueryPermission(FileSystemHandleInfo handle, bool write = false)
The current permission, without prompting.
RequestPermission
ValueTask<FileSystemPermission> RequestPermission(FileSystemHandleInfo handle, bool write = false)
Prompts for permission - what makes a handle restored from a previous session usable again. Requires a user gesture.
Release
ValueTask Release(FileSystemHandleInfo handle)
Drops a handle you're finished with.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, drops every handle this app was holding.
An unhandled error has occurred. Reload 🗙