Dateien direkt im Browser lesen und schreiben
Die File System Access API erlaubt Webanwendungen echten Lese- und Schreibzugriff auf lokale Dateien und Ordner, ohne den klassischen Umweg über Datei-Uploads und Downloads. Wer Editoren, Bildwerkzeuge oder Datenexporte im Browser baut, erhält damit ein Verhalten, das sich fast wie eine native Desktop-Anwendung anfühlt, inklusive Berechtigungsmodell und persistenten Handles.
Inhaltsverzeichnis
- 1. Warum die File System Access API existiert
- 2. Sicherheitsmodell: Secure Context, Geste und Herkunft
- 3. Dateien öffnen mit showOpenFilePicker
- 4. Dateien schreiben mit showSaveFilePicker
- 5. Verzeichnisse durchsuchen mit showDirectoryPicker
- 6. Handles persistieren und Berechtigungen erneuern
- 7. Große Dateien und Streaming-Schreibzugriff
- 8. Fallback-Strategien für Safari und Firefox
- 9. File System Access API im Vergleich zu klassischen Mustern
- 10. Zusammenfassung
- 11. FAQ
1. Warum die File System Access API existiert
Vor der File System Access API war der Umgang mit lokalen Dateien im Browser ein Kompromiss. Ein <input type="file"> lieferte eine einmalige Momentaufnahme einer Datei als File-Objekt, jede Änderung musste über einen neuen Download als frische Kopie an den Nutzer zurückgegeben werden. Es gab keine Möglichkeit, dieselbe Datei erneut zu öffnen, in sie hineinzuschreiben oder ein Verzeichnis strukturiert zu durchsuchen. Für Text-Editoren, Bildbearbeitung oder Entwicklerwerkzeuge im Browser war das ein spürbarer Rückstand gegenüber nativen Anwendungen.
Die File System Access API schließt genau diese Lücke. Sie stellt drei zentrale Einstiegspunkte bereit, window.showOpenFilePicker(), window.showSaveFilePicker() und window.showDirectoryPicker(), die jeweils einen nativen Dateidialog öffnen und ein FileSystemHandle zurückgeben. Über dieses Handle lässt sich eine Datei wiederholt lesen, überschreiben oder ein ganzes Verzeichnis rekursiv durchlaufen, ohne dass der Nutzer bei jedem Schritt erneut einen Dialog bestätigen muss. Das macht Web-Editoren wie VS Code for the Web oder Photopea erst praktisch nutzbar.
Wichtig ist, die File System Access API nicht mit der älteren, längst zurückgezogenen File and Directory Entries API zu verwechseln, die nur read-only Zugriff über webkitdirectory bot. Die neue API ist bewusst als Standard bei der WHATWG entstanden und wird kontinuierlich um Fähigkeiten wie synchronen Zugriff aus Web Workern erweitert, was sie auch für performance-kritische Anwendungen wie WebAssembly-Datenbanken interessant macht.
2. Sicherheitsmodell: Secure Context, Geste und Herkunft
Weil die File System Access API echten Schreibzugriff auf das lokale Dateisystem gewährt, ist ihr Berechtigungsmodell strenger als bei den meisten anderen Browser APIs. Jeder Aufruf von showOpenFilePicker, showSaveFilePicker oder showDirectoryPicker muss aus einer aktiven Nutzergeste heraus erfolgen, ein Klick oder Tastendruck. Ein Aufruf aus einem setTimeout oder direkt beim Laden der Seite wird vom Browser mit einem SecurityError abgelehnt.
Zusätzlich funktioniert die File System Access API ausschließlich in einem Secure Context, also über HTTPS oder auf localhost. Innerhalb eines <iframe> ist der Zugriff standardmäßig deaktiviert, es sei denn, das Elternfenster erlaubt es explizit über die Permissions-Policy fullscreen-artige Direktive window-management beziehungsweise die relevante Feature-Policy für Dateisystemzugriff. Diese Kombination aus Nutzergeste, Secure Context und expliziter Erlaubnis verhindert, dass eine eingebettete Werbung oder ein Drittanbieter-Skript unbemerkt Dateien öffnet.
Jede erteilte Berechtigung ist zudem an die konkrete Datei oder das konkrete Verzeichnis gebunden, nicht an die gesamte Festplatte. Ein Nutzer erlaubt gezielt den Zugriff auf ein Projektverzeichnis, nicht auf sein gesamtes Home-Verzeichnis. Das Sicherheitsmodell der File System Access API folgt damit demselben Prinzip wie bei Kamera oder Standort, granular, widerrufbar und immer sichtbar für den Nutzer über die Adressleisten-Berechtigungen.
3. Dateien öffnen mit showOpenFilePicker
Der Einstieg in die File System Access API gelingt am einfachsten über showOpenFilePicker(). Die Methode ist asynchron, gibt ein Array von FileSystemFileHandle-Objekten zurück und akzeptiert Optionen wie types zur Einschränkung auf bestimmte Dateiendungen und multiple für Mehrfachauswahl. Aus dem Handle lässt sich über getFile() jederzeit ein aktuelles File-Objekt anfordern, das sich dann wie gewohnt mit text(), arrayBuffer() oder stream() lesen lässt.
Der entscheidende Unterschied zum klassischen <input type="file">: Das Handle bleibt gültig, solange die Seite offen ist oder solange es persistiert wurde. Eine Anwendung kann also dasselbe Handle später erneut verwenden, um die Datei neu einzulesen, etwa nachdem der Nutzer sie extern in einem anderen Programm geändert hat. Genau dieses Verhalten macht Editoren im Browser praxistauglich.
// Open a text file and read its current content
async function openTextFile() {
try {
const [fileHandle] = await window.showOpenFilePicker({
types: [
{
description: "Text files",
accept: { "text/plain": [".txt", ".md"] },
},
],
multiple: false,
});
const file = await fileHandle.getFile();
const contents = await file.text();
console.log(`Opened ${file.name}, ${file.size} bytes`);
// Keep the handle around so we can write back to the
// exact same file later without asking again.
return { fileHandle, contents };
} catch (err) {
if (err.name === "AbortError") {
console.log("User cancelled the file picker");
return null;
}
throw err;
}
}
4. Dateien schreiben mit showSaveFilePicker
Zum Schreiben stellt die File System Access API showSaveFilePicker() bereit. Anders als beim klassischen <a download>-Muster fragt der Browser den Zielort einmalig ab und liefert danach ein Handle, über das beliebig oft geschrieben werden kann, ohne dass jedes Mal ein Speicherdialog erscheint. Zum eigentlichen Schreiben öffnet man mit createWritable() einen FileSystemWritableFileStream, schreibt Daten hinein und schließt den Stream mit close() ab, erst dieser Abschluss macht die Änderungen dauerhaft sichtbar.
Ein häufiger Fehler bei der File System Access API ist, den Stream nicht zu schließen, etwa weil eine Exception den close()-Aufruf überspringt. Die Datei bleibt dann in einem inkonsistenten Zustand, meist als leere temporäre Datei sichtbar. Ein try/finally-Block um den Schreibvorgang stellt sicher, dass der Stream in jedem Fall geschlossen wird, ähnlich wie man es von Dateizugriffen in anderen Sprachen kennt.
// Save (or overwrite) a file through the File System Access API
async function saveTextFile(fileHandle, content) {
// Ask for a location only if we don't already have a handle
if (!fileHandle) {
fileHandle = await window.showSaveFilePicker({
suggestedName: "notes.txt",
types: [
{ description: "Text file", accept: { "text/plain": [".txt"] } },
],
});
}
const writable = await fileHandle.createWritable();
try {
await writable.write(content);
} finally {
// Without close(), changes never reach the actual file on disk.
await writable.close();
}
return fileHandle;
}
5. Verzeichnisse durchsuchen mit showDirectoryPicker
Für Anwendungen, die ganze Projektstrukturen im Browser abbilden wollen, bringt die File System Access API mit showDirectoryPicker() einen dritten Einstiegspunkt. Die Methode liefert ein FileSystemDirectoryHandle, das sich mit for await (const [name, handle] of directoryHandle.entries()) asynchron durchlaufen lässt. Jeder Eintrag ist entweder ein FileSystemFileHandle oder ein weiteres FileSystemDirectoryHandle, was rekursives Durchsuchen ganzer Verzeichnisbäume ermöglicht.
Diese Fähigkeit der File System Access API ist die Grundlage für browserbasierte IDEs, die ein komplettes Projektverzeichnis öffnen, alle Dateien indexieren und Änderungen direkt auf der Festplatte speichern, ohne ein Backend-Dateisystem zu benötigen. Die Methode getFileHandle(name, { create: true }) beziehungsweise getDirectoryHandle(name, { create: true }) erlaubt es zusätzlich, neue Dateien und Unterordner innerhalb eines bereits autorisierten Verzeichnisses anzulegen, ohne dafür erneut einen Dialog zu öffnen.
// Recursively list every file in a directory tree
async function listAllFiles(directoryHandle, path = "") {
const files = [];
for await (const [name, handle] of directoryHandle.entries()) {
const fullPath = path ? `${path}/${name}` : name;
if (handle.kind === "file") {
files.push(fullPath);
} else if (handle.kind === "directory") {
// Recurse into subdirectories without asking the user again
const nested = await listAllFiles(handle, fullPath);
files.push(...nested);
}
}
return files;
}
const rootHandle = await window.showDirectoryPicker();
const allFiles = await listAllFiles(rootHandle);
console.log(`Found ${allFiles.length} files`);
6. Handles persistieren und Berechtigungen erneuern
Ein Handle der File System Access API ist ein strukturierter, klonbarer Wert und lässt sich daher direkt in IndexedDB speichern. Damit kann eine Anwendung beim nächsten Seitenaufruf denselben Projektordner ohne erneuten Dateidialog wieder öffnen, sofern der Nutzer die Berechtigung nicht zwischenzeitlich widerrufen hat. Genau hier liegt der entscheidende Unterschied zur bloßen Referenz: Der Browser speichert die Berechtigung nicht dauerhaft automatisch, sie muss über queryPermission() geprüft und bei Bedarf über requestPermission() erneuert werden.
Diese erneute Anfrage erfordert wiederum eine Nutzergeste, kann also nicht beim automatischen Seitenladen im Hintergrund erfolgen. Die übliche Lösung: Beim Start prüft die Anwendung stillschweigend mit queryPermission({ mode: "readwrite" }), ob die Berechtigung noch besteht, und zeigt nur dann einen Button "Zugriff erneut erlauben" an, wenn der Status "prompt" statt "granted" zurückkommt. Das vermeidet unnötige Dialoge und erklärt dem Nutzer gleichzeitig, warum eine Aktion nötig ist.
// Persist a handle in IndexedDB and re-request permission later
const DB_NAME = "file-handles";
const STORE_NAME = "handles";
async function saveHandle(key, handle) {
const db = await openHandleDb();
const tx = db.transaction(STORE_NAME, "readwrite");
tx.objectStore(STORE_NAME).put(handle, key);
await tx.done;
}
async function restoreHandle(key) {
const db = await openHandleDb();
const handle = await db.transaction(STORE_NAME).objectStore(STORE_NAME).get(key);
if (!handle) return null;
const status = await handle.queryPermission({ mode: "readwrite" });
if (status === "granted") return handle;
// Re-requesting permission always needs a user gesture (e.g. click)
const granted = await handle.requestPermission({ mode: "readwrite" });
return granted === "granted" ? handle : null;
}
7. Große Dateien und Streaming-Schreibzugriff
Ein oft übersehener Vorteil der File System Access API gegenüber Blob-Downloads liegt bei großen Dateien. Ein klassischer Download über URL.createObjectURL() benötigt den gesamten Dateiinhalt zunächst als Blob im Arbeitsspeicher, bevor er geschrieben werden kann. Der FileSystemWritableFileStream dagegen ist ein echter WritableStream, der Daten häppchenweise auf die Festplatte schreibt, während sie erzeugt werden, etwa direkt aus einem Netzwerk-Response via pipeTo().
Für Video-Exporte, Datenbank-Dumps oder große CSV-Exporte reduziert das den Speicherbedarf drastisch, weil nie der komplette Inhalt gleichzeitig im RAM liegen muss. Zusätzlich unterstützt die File System Access API in Web Workern seit einiger Zeit den synchronen Zugriff über createSyncAccessHandle(), der für Datei-basierte SQLite-Kompilate wie wa-sqlite spürbar schneller ist als der asynchrone Pfad, weil er ohne Promise-Overhead direkt blockierend liest und schreibt.
8. Fallback-Strategien für Safari und Firefox
Safari und Firefox unterstützen die File System Access API bislang nicht vollständig, weshalb produktiver Code stets eine Feature-Erkennung benötigt. Die zuverlässigste Prüfung ist "showOpenFilePicker" in window, da reine User-Agent-Sniffing-Ansätze bei Browser-Updates schnell veralten. Fehlt die API, greift man auf das klassische Muster mit <input type="file"> zum Lesen und einem programmatisch erzeugten <a download>-Link zum Speichern zurück.
Wichtig ist, dass der Fallback dieselbe äußere Schnittstelle bereitstellt, damit der restliche Anwendungscode nicht zwischen zwei APIs unterscheiden muss. Eine kleine Abstraktionsschicht, die entweder ein echtes FileSystemFileHandle oder ein simuliertes Objekt mit denselben Methodennamen zurückgibt, hält den Rest der Codebasis frei von Verzweigungen. Bibliotheken wie browser-fs-access implementieren genau dieses Muster bereits fertig und sind für viele Projekte der pragmatischere Weg als eine Eigenentwicklung.
// Feature detection with a graceful fallback
async function pickAndSaveFile(content, suggestedName) {
if ("showSaveFilePicker" in window) {
const handle = await window.showSaveFilePicker({ suggestedName });
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
return;
}
// Fallback for Safari/Firefox: classic download link
const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = suggestedName;
link.click();
URL.revokeObjectURL(url);
}
9. File System Access API im Vergleich zu klassischen Mustern
Die Entscheidung zwischen der File System Access API und den klassischen Browser-Mustern hängt stark vom Anwendungsfall ab. Für einmalige Uploads reicht oft weiterhin ein simples <input type="file">, für wiederholtes Lesen und Schreiben derselben Datei ist die neue API jedoch klar überlegen.
| Anforderung | Klassisches Muster | File System Access API | Vorteil |
|---|---|---|---|
| Datei erneut öffnen | Nicht möglich ohne neuen Upload | FileSystemFileHandle |
Persistentes Handle statt Momentaufnahme |
| In dieselbe Datei schreiben | Nur Download als neue Kopie | createWritable() |
Überschreiben statt Duplizieren |
| Verzeichnis durchsuchen | webkitdirectory (read-only) |
showDirectoryPicker() |
Lesen und Schreiben rekursiv |
| Große Dateien | Blob komplett im RAM | Streaming-Schreibzugriff | Geringerer Speicherbedarf |
| Browser-Support | Alle Browser | Nur Chromium-Basis | Fallback zwingend nötig |
In der Praxis kombinieren erfolgreiche Anwendungen beide Ansätze: Die File System Access API wird genutzt, wo sie verfügbar ist, während der klassische Upload/Download-Weg als Fallback für Safari und Firefox erhalten bleibt. Diese doppelte Strategie sorgt dafür, dass die Anwendung überall funktioniert, während Chromium-Nutzer von der komfortableren, direkteren Dateiverwaltung profitieren.
Mironsoft
JavaScript-Entwicklung, Browser APIs und moderne Web-Anwendungen
Editor- oder Datei-Feature im Browser geplant?
Wir integrieren die File System Access API in eure Web-Anwendung, inklusive Berechtigungsmodell, Persistenz über IndexedDB und robustem Fallback für Safari und Firefox.
API-Integration
showOpenFilePicker, showSaveFilePicker und showDirectoryPicker sauber angebunden
Fallback-Konzept
Robuste Feature-Detection und Upload/Download-Alternative für alle Browser
Performance-Tuning
Streaming-Schreibzugriff für große Dateien statt Blob im Arbeitsspeicher
10. Zusammenfassung
Die File System Access API hebt den Umgang mit lokalen Dateien im Browser auf ein Niveau, das sich fast wie eine native Anwendung anfühlt. Statt einmaliger Uploads und neuer Downloads bei jeder Änderung erlaubt sie persistente FileSystemHandle-Objekte, die wiederholtes Lesen, Schreiben und rekursives Durchsuchen von Verzeichnissen ermöglichen. Das Sicherheitsmodell mit Nutzergeste, Secure Context und granularen Berechtigungen sorgt dafür, dass diese Mächtigkeit nicht zum Einfallstor für unbemerkten Dateizugriff wird.
Wer die File System Access API produktiv einsetzt, sollte drei Dinge beachten: Handles konsequent in IndexedDB persistieren, Berechtigungen bei jedem Seitenaufruf über queryPermission() prüfen statt blind anzunehmen, und immer eine Fallback-Strategie für Safari und Firefox bereithalten. Mit dieser Kombination lassen sich Web-Editoren, Datei-Manager und Exportwerkzeuge bauen, die in unterstützten Browsern das volle native Erlebnis bieten und überall sonst zuverlässig degradieren.
File System Access API praktisch nutzen — Das Wichtigste auf einen Blick
Drei Einstiegspunkte
showOpenFilePicker, showSaveFilePicker und showDirectoryPicker liefern jeweils ein persistentes FileSystemHandle.
Berechtigungsmodell
Jeder Aufruf braucht eine Nutzergeste und einen Secure Context. Berechtigungen sind pro Datei granular und widerrufbar.
Persistenz
Handles in IndexedDB speichern, Berechtigung beim nächsten Aufruf mit queryPermission() prüfen statt neu erfragen.
Fallback
Feature-Detection mit "showOpenFilePicker" in window, Upload/Download-Muster für Safari und Firefox bereithalten.