File Handling in Flutter Web: Picking, Reading, Drag and Drop, and Saving
A practical guide to file handling in Flutter web: file_picker, drag and drop, object URLs, and saving files through the browser.
Published on • November 10, 2026
AI Assistant

File handling in Flutter web runs through the browser’s own sandbox, which means some desktop habits work unchanged and others have no equivalent at all. The reliable path is to pick files with a package, read them with the browser’s FileReader, hand small blobs to the page with object URLs, and treat saving as a download. This post covers the full loop with code you can paste into a project.
Pick files with file_picker
The file_picker package is the standard entry point, and its web implementation is the federated file_picker_web package. Recent versions of the API are pick-first: FilePicker.pickFile() returns a single PlatformFile? (null means the user cancelled), and FilePicker.pickFiles() returns a List<PlatformFile> where an empty list means cancelled. On 13.x you can also pass FilePickerWebOptions to the web implementation, but only through a conditional import guarded by if (dart.library.js_interop), because those types exist only on the web.
import 'package:file_picker/file_picker.dart';
Future<void> pickOne() async {
final PlatformFile? file = await FilePicker.pickFile(
type: FileType.custom,
allowedExtensions: ['csv', 'json', 'txt'],
);
if (file == null) return; // user cancelled
final bytes = await file.readAsBytes();
print('${file.name}: ${bytes.length} bytes');
}
Future<void> pickMany() async {
final files = await FilePicker.pickFiles(allowMultiple: true);
if (files.isEmpty) return; // cancelled
for (final f in files) {
final size = await f.length() ?? 0;
print('${f.name} - $size bytes');
}
}
Know the platform limits before you design around them. On web, getDirectoryPath() and clearTemporaryFiles() do not work, while pickFile, pickFiles, and saveFile do. path_provider is not a fallback either: it supports Android, iOS, Linux, macOS, and Windows, but not web, so temp directories and app-support paths must stay on native platforms.
Read bytes from a PlatformFile
PlatformFile on web wraps a browser File, and readAsBytes() is the convenient path for small and medium files. If you are already in package:web land, the underlying read is a FileReader: create it, call readAsArrayBuffer(blob), await the loadend event, then take the result as a JSArrayBuffer you can convert into dart:typed_data bytes.
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:web/web.dart' as web;
Future<Uint8List> readAll(web.File file) async {
final reader = web.FileReader();
final done = reader.onLoadEnd.first;
reader.readAsArrayBuffer(file);
await done;
final buffer = reader.result! as JSArrayBuffer;
return buffer.toDart.asUint8List();
}
Keep the read proportional to the file: a hundred-megabyte upload should stream or chunk rather than materializing one giant Uint8List in the JS heap, because the browser will happily allocate it until it does not.
Accept files by drag and drop
Drag and drop needs no package at all: listen for drop on a widget-level listener, read event.dataTransfer?.files (a FileList), and feed each entry back into the same read path. The browser hands you the files directly with no picker dialog.
import 'package:web/web.dart' as web;
void onDrop(web.DragEvent event) {
final files = event.dataTransfer?.files;
if (files == null) return;
for (var i = 0; i < files.length; i++) {
final file = files.item(i);
if (file != null) {
// Read with FileReader as shown above
}
}
}
Pair the drop zone with pickFiles() as the keyboard-accessible alternative, and validate extensions on both paths since either one can deliver an unexpected file type.
Share a blob with the page using object URLs
Sometimes you do not want the file in Dart at all; you want the browser to display or download it. URL.createObjectURL() creates a string URL for any blob, including File objects and generated Blobs, and the string must be released with URL.revokeObjectURL() once you are done, or the blob stays pinned in memory for the lifetime of the document.
import 'package:web/web.dart' as web;
String attachPreview(web.File file) => web.URL.createObjectURL(file);
void detachPreview(String objectUrl) => web.URL.revokeObjectURL(objectUrl);
That object URL is what you assign to an <img> or <video> element’s src, or hand to an anchor so the browser opens a generated PDF without a round trip.
Save files the browser way
Saving is where the sandbox shows. There is no path you can write to, so the baseline is a download: build a Blob, make an object URL, and click a synthetic anchor. file_picker’s saveFile() does exactly this on web by default, and the modern alternative is the File System Access API’s showSaveFilePicker, which opens a real save dialog, returns a FileSystemFileHandle, and lets you write a stream. It requires a secure context and a transient user activation, it is still marked “Limited availability” and experimental, and it throws AbortError when the user cancels or SecurityError when the context is not allowed.
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
Future<void> saveReport(List<int> bytes) async {
final Uri? uri = await FilePicker.saveFile(
dialogTitle: 'Save report',
fileName: 'report.csv',
bytes: Uint8List.fromList(bytes),
);
// On web this is a download; uri may be null if the user cancelled.
if (uri == null) return;
}
If you need the true dialog with a user-chosen location, feature-detect showSaveFilePicker first and fall back to the download path everywhere else, including all iOS and Firefox browsers.
Check your limits
End with a pass through real browsers: pick a file on mobile Safari, drag a multi-file selection onto the page on Chrome, download a generated CSV, and confirm no SecurityError or CORS failure lands in the console. The failure modes are predictable: cancelled dialogs returning null or empty lists, unsupported directory picks, and object URLs never revoked.
Takeaway: Treat the browser as the file system on Flutter web: pick with file_picker, read with FileReader, share blobs through object URLs you revoke, and save as a download with showSaveFilePicker only as an optional upgrade.