Skip to content
Blog

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; null on 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) before writeAsString; missing directories throw, they are not created for you.
  • Nullable results (getDownloadsDirectory, external storage) need real null handling, 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.