File System Paths in Flutter: A Practical Guide to path_provider
Resolve temp, documents, support, downloads, and external directories in Flutter with path_provider, including web guards and platform mocks for tests.
Published on • November 2, 2026
AI Assistant

Flutter apps cannot hardcode file locations, and path_provider (2.1.6, a Flutter Favorite) is the standard way to find the common filesystem locations the OS expects you to use on Android (SDK 24+), iOS (13.0+), Linux, macOS (10.15+), and Windows 10+ (pub.dev).
What Each Directory Is For
getTemporaryDirectory()— scratch space the OS may clear without asking; caches and offline copies.getApplicationDocumentsDirectory()— durable user data the user would hate to lose; backup-eligible on iOS.getApplicationSupportDirectory()— internal app-managed config that is not user content and stays hidden.getDownloadsDirectory()— the user’s Downloads folder; nullable and not implemented everywhere.getExternalStorageDirectory()— Android-only external storage;nullon every other platform.
There is no API for arbitrary paths, and none of it exists on web — IndexedDB is not a file system.
Guarding for Web and Desktop
Return early on web, or better, hide the platform code behind a conditional import such as export 'store_io.dart' if (dart.library.js_interop) 'store_web.dart';, so release builds never touch dart:io on the web:
Future<String?> notesDir() async {
if (kIsWeb) return null;
final dir = await getApplicationDocumentsDirectory();
return p.join(dir.path, 'notes');
}
Writing and Reading a File End-to-End
Join paths with package:path (p.join) for correct separators on Windows, create the directory first, then read back with file.readAsString() (checking existsSync() after a restore or cache wipe):
Future<File> draftFile() async {
final dir = await getApplicationDocumentsDirectory();
final file = File(p.join(dir.path, 'notes', 'draft.md'));
await file.parent.create(recursive: true); // dirs are never implicit
return file;
}
Future<void> saveNote(String body) async =>
(await draftFile()).writeAsString(body, flush: true);
Testing with a Mocked PathProviderPlatform
The plugin routes through a PlatformInterface, not a raw PlatformChannel, so tests just replace PathProviderPlatform.instance — and assertions should use p.join(...) so the suite also passes on Windows CI:
class FakePathProvider extends PathProviderPlatform {
FakePathProvider(this.root);
final String root;
@override
Future<String?> getApplicationDocumentsDirectory() async => root;
}
void main() {
setUp(() => PathProviderPlatform.instance = FakePathProvider('/tmp/fake'));
test('saveNote round-trips', () async {
await saveNote('hello');
expect(await readNote(), 'hello');
});
}
Gotchas and Best Practices
- Never store user files in
getTemporaryDirectory()— iOS and Android purge it under storage pressure. - Call
create(recursive: true)beforewriteAsString; missing directories throw, they are not created for you. - Nullable results (
getDownloadsDirectory, external storage) need realnullhandling, not!. - Stream large files with
openWrite()instead of buffering whole blobs in memory.
Match the directory to your data’s lifetime, guard for web, join paths with package:path, and mock PathProviderPlatform in tests.