os_paths 1.0.0 copy "os_paths: ^1.0.0" to clipboard
os_paths: ^1.0.0 copied to clipboard

OS-specific standard directories (XDG on Linux/BSD, ~/Library on macOS, %APPDATA% on Windows) for Flutter and Dart apps.

os_paths #

OS-specific standard directories for Flutter and Dart — XDG on Linux/BSD, ~/Library on macOS, %APPDATA% on Windows.

Pure Dart: no plugin registration, no platform channels, no dependencies. The same code works in a Flutter desktop app, a Flutter test, and a dart:io command-line program.

Usage #

import 'package:os_paths/os_paths.dart';

final paths = OsPaths.instance;

paths.configHome;   // ~/.config              | ~/Library/Application Support | %APPDATA%
paths.dataHome;     // ~/.local/share         | ~/Library/Application Support | %APPDATA%
paths.cacheHome;    // ~/.cache               | ~/Library/Caches              | %LOCALAPPDATA%
paths.stateHome;    // ~/.local/state         | ~/Library/Application Support | %LOCALAPPDATA%
paths.runtimeDir;   // $XDG_RUNTIME_DIR       | null                          | null
paths.documentsDir; // ~/Documents (localized via xdg-user-dirs)

Most applications want directories scoped to themselves:

import 'dart:io';

final paths = OsPaths.instance;
final app = paths.app('Fern Notes', organization: 'Lexmata');

final configDir = await app.ensureConfig(); // created if missing
final settings = File(paths.join(configDir.path, 'settings.json'));
await settings.writeAsString('{"theme":"dark"}');
Linux macOS Windows
app.data ~/.local/share/fern-notes ~/Library/Application Support/Lexmata/Fern Notes %APPDATA%\Lexmata\Fern Notes
app.config ~/.config/fern-notes ~/Library/Application Support/Lexmata/Fern Notes %APPDATA%\Lexmata\Fern Notes
app.cache ~/.cache/fern-notes ~/Library/Caches/Lexmata/Fern Notes %LOCALAPPDATA%\Lexmata\Fern Notes\Cache
app.logs ~/.local/state/logs/fern-notes ~/Library/Logs/Lexmata/Fern Notes %LOCALAPPDATA%\Lexmata\Fern Notes\Logs

organization is ignored on the XDG platforms (Linux, the BSDs, Fuchsia), where the convention is a single lowercase directory named after the application. Windows has no cache or log Known Folder, so those two get a subdirectory below the application's own directory there — otherwise they would collide with app.state.

Application names are sanitized: illegal characters are stripped, Windows reserved device names (CON, NUL, ...) are escaped, and a name can never contribute more than one path segment or escape its base directory.

What is resolved #

Getter Linux / BSD macOS Windows
home $HOME $HOME, else /Users/$USER (or /var/root) %USERPROFILE%, else %HOMEDRIVE%%HOMEPATH%
dataHome $XDG_DATA_HOME, else ~/.local/share ~/Library/Application Support %APPDATA%
localDataHome same as dataHome same as dataHome %LOCALAPPDATA%
configHome $XDG_CONFIG_HOME, else ~/.config ~/Library/Application Support %APPDATA%
cacheHome $XDG_CACHE_HOME, else ~/.cache ~/Library/Caches %LOCALAPPDATA%
stateHome $XDG_STATE_HOME, else ~/.local/state ~/Library/Application Support %LOCALAPPDATA%
binHome $XDG_BIN_HOME (non-standard extension), else ~/.local/bin ~/Applications (application bundles, not on PATH) %LOCALAPPDATA%\Programs
preferencesDir = configHome ~/Library/Preferences = configHome
logHome ~/.local/state/logs ~/Library/Logs %LOCALAPPDATA%
runtimeDir $XDG_RUNTIME_DIR or null null null
tempDir $TMPDIR, else /tmp $TMPDIR, else /tmp %TEMP%, else %TMP%, else %LOCALAPPDATA%\Temp
dataDirs $XDG_DATA_DIRS /Library/Application Support %ProgramData%
configDirs $XDG_CONFIG_DIRS /Library/Preferences %ProgramData%
fontsDir ~/.local/share/fonts ~/Library/Fonts %LOCALAPPDATA%\Microsoft\Windows\Fonts

User media — desktopDir, documentsDir, downloadsDir, musicDir, picturesDir, videosDir, templatesDir, publicShareDir — hangs off the home directory on macOS, and on Windows for everything except templatesDir (%APPDATA%\Microsoft\Windows\Templates) and publicShareDir (%PUBLIC%). On Linux the exported XDG_*_DIR variables win, then $XDG_CONFIG_HOME/user-dirs.dirs (so localized names like ~/Bureau resolve correctly), then ~/Desktop and friends.

runtimeDir is null wherever the platform has no such concept; fall back to tempDir.

A value that is not an absolute path is ignored, as if the variable were unset — the XDG specification requires this for XDG_*, and the same rule is applied to every platform's directory variables, since a relative value would resolve against the process's working directory instead of the user's profile. On Windows "absolute" means a drive root (C:\) or a UNC prefix (\\server); a drive-relative C:Users\ada is rejected. Every returned path has its trailing separator stripped, so results compare and compose predictably.

These directories are not always distinct #

Not every platform separates these concepts:

Collapses to one directory
Windows configHome = dataHome; cacheHome = stateHome = logHome = localDataHome
macOS dataHome = configHome = stateHome

Never assume two of them differ — in particular, never recursively delete one to "reset" a single category. AppPaths does keep an application's cache and logs in their own subdirectories on every platform.

Android, iOS, and unknown platforms #

These platforms hand out sandboxed paths at runtime, so nothing usable can be derived from the environment. Getters that resolve a single directory throw OsPathsException there; dataDirs/configDirs are empty and runtimeDir is null, because a sandbox has no machine-wide directories. Wire the real directories once at startup, and the rest of the API works unchanged:

import 'package:path_provider/path_provider.dart';

Future<void> initPaths() async {
  // needsExplicitPaths, not isMobile: it also covers OsFamily.unknown, which
  // would otherwise be left unconfigured.
  if (!OsFamily.current.needsExplicitPaths) return;
  OsPaths.instance = ExplicitPaths(
    family: OsFamily.current,
    home: (await getApplicationDocumentsDirectory()).path,
    dataHome: (await getApplicationSupportDirectory()).path,
    cacheHome: (await getTemporaryDirectory()).path,
    documentsDir: (await getApplicationDocumentsDirectory()).path,
  );
}

Isolates do not share OsPaths.instance. Dart statics are per-isolate, so a resolver installed on the main isolate is invisible inside Isolate.spawn or Flutter's compute() — a background isolate rebuilds the host resolver and every getter throws. Install one at the top of the isolate's entry point, or pass the directories you need across the boundary.

Testing #

Every platform's behaviour can be exercised from any host by supplying a family and an environment:

final paths = OsPaths(
  family: OsFamily.windows,
  environment: {'USERPROFILE': r'C:\Users\ada', 'APPDATA': r'C:\Users\ada\AppData\Roaming'},
);

expect(paths.app('Fern Notes').config, r'C:\Users\ada\AppData\Roaming\Fern Notes');

Reading user-dirs.dirs goes through an injectable FileReader, so tests never touch the disk. Set OsPaths.instance to swap the resolver globally, and use addTearDown(OsPaths.resetInstance) to restore the default.

Caveats #

  • Windows Known Folders that the user has relocated (Documents redirected into OneDrive, for example) are not detected — that needs SHGetKnownFolderPath, which would require a plugin. Override the affected directory explicitly: ExplicitPaths(documentsDir: ..., ...).
  • No directory is created unless you call one of the ensure* methods. Those throw FileSystemException (a dart:io error, not OsPathsException) when creation fails, and they follow symbolic links — resolve the result and compare it against the expected base if that is part of your threat model.
  • tempDir falls back to /tmp when $TMPDIR is unset. /tmp is shared and world-writable, and AppPaths.temp under it is a predictable name; use Directory.systemTemp.createTemp() when you need a private directory.
  • The web is not supported: this package imports dart:io, which does not compile for web targets.
  • parseUserDirs follows the reference implementation and silently ignores entries it cannot resolve (relative values, ~, ${HOME}, unterminated quotes), falling back to the English defaults.
  • user-dirs.dirs is read once per resolver and cached, including a miss. Construct a new OsPaths to pick up later changes.

Conformance #

  • Relative values in environment variables are ignored, as the XDG specification requires, rather than returned as-is.
  • Windows cacheHome/logHome are %LOCALAPPDATA% — there is no cache or log Known Folder — and AppPaths nests Cache and Logs below the application directory so they stay distinct from state.
  • Application names are sanitized so they cannot escape their base directory, contribute more than one path segment, or collide with a Windows reserved device name.
  • user-dirs.dirs parsing follows the xdg-user-dirs reference implementation, including its backslash escapes and its silent skipping of entries that cannot be resolved.

License #

MIT — see LICENSE.

Copyright (c) 2026 Joseph R. Quinn.

0
likes
160
points
7
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

OS-specific standard directories (XDG on Linux/BSD, ~/Library on macOS, %APPDATA% on Windows) for Flutter and Dart apps.

Repository (GitHub)
View/report issues

Topics

#paths #filesystem #xdg #directories

License

MIT (license)

More

Packages that depend on os_paths