cheat sheet

platformdirs

Package-level reference for platformdirs on PyPI — install, version policy, alternatives, and the per-OS path conventions it returns.

platformdirs

What it is

platformdirs is a tiny utility library that returns the correct per-OS location for an application's config, cache, data, state, and log directories. It is the maintained successor to the long-defunct appdirs package — same idea, modern API, active releases, and proper type hints.

Reach for it whenever a CLI or desktop app needs to write or read user-specific files in the platform's expected location: ~/.config/myapp on Linux, ~/Library/Application Support/myapp on macOS, %APPDATA%\myapp on Windows. Hard-coding ~/.myapp works on Linux but breaks platform conventions everywhere else, and tools like backup utilities, sandboxers, and "clear cache" UIs rely on those conventions.

Install

bash
pip install platformdirs

Output: (none — exits 0 on success)

bash
uv add platformdirs

Output: dependency resolved + added to pyproject.toml

bash
poetry add platformdirs

Output: updated lockfile + virtualenv install

bash
pipx inject myapp platformdirs    # inject into an existing pipx-installed CLI

Output: package installed into the named app's isolated venv

Versioning & Python support

  • Stable 4.x line as of mid-2026. Semantic-versioned and very small surface area — bumps are usually safe.
  • Supports Python 3.8+ on recent releases; 4.x requires 3.8 minimum.
  • The appdirs predecessor stopped at 1.4.4 in 2020 and is abandoned. platformdirs is a drop-in replacement from a process standpoint — the function names changed (user_config_dir instead of user_config_dir — yes, identical for that one), but the import path is platformdirs, not appdirs.

Package metadata

  • Maintainer: Bernát Gábor (core) and the platformdirs GitHub org
  • Project home: github.com/platformdirs/platformdirs
  • Docs: platformdirs.readthedocs.io
  • PyPI: pypi.org/project/platformdirs
  • License: MIT
  • First released: 2021 (as the appdirs fork)
  • Downloads: hundreds of millions per month — pulled in transitively by pip, poetry, virtualenv, tox, black, and many others

Optional dependencies & extras

platformdirs has zero required runtime dependencies. The two extras are documentation and type-checker quality-of-life:

  • platformdirs[docs] — Sphinx + theme for building the docs locally.
  • platformdirs[type]mypy plugin metadata; rarely needed because the package ships its own py.typed marker.

There are no compiled extensions, no transitive deps that pull in compilers, no native binaries. This is part of why so many packaging tools depend on it — it's safe to add to every install.

Alternatives

PackageTrade-off
appdirsAbandoned since 2020. Identical idea, smaller maintenance pulse. Use only if a legacy codebase already imports it.
xdg-base-dirsLinux-only, strict XDG-spec implementation. Use when you only target Linux and want zero Windows/macOS noise.
Manual os.environ + pathlibZero deps. Fine for one path, painful for the full five-directory matrix and the macOS sandbox cases.
click.get_app_dirBundled with click. Works only inside click apps; returns one path, not the full set.

Real-world recipes

The examples below are deliberately short — platformdirs is a one-function-per-line library. The value is knowing which call to reach for in which scenario.

Recipe 1 — User config dir for app settings (config.toml, settings.json).

python
from pathlib import Path
from platformdirs import user_config_dir

cfg_dir = Path(user_config_dir("myapp", "myorg"))
cfg_dir.mkdir(parents=True, exist_ok=True)
cfg_file = cfg_dir / "config.toml"
print(cfg_file)

Output (Linux): /home/alice/.config/myapp/config.toml Output (macOS): /Users/alice/Library/Application Support/myapp/config.toml Output (Windows): C:\Users\alice\AppData\Local\myorg\myapp\config.toml

Recipe 2 — User cache dir for ephemeral, regenerable downloads.

python
from pathlib import Path
from platformdirs import user_cache_dir

cache = Path(user_cache_dir("myapp"))
cache.mkdir(parents=True, exist_ok=True)
(cache / "image-thumbs").mkdir(exist_ok=True)

Output (Linux): /home/alice/.cache/myapp/image-thumbs Output (macOS): /Users/alice/Library/Caches/myapp/image-thumbs Output (Windows): C:\Users\alice\AppData\Local\myapp\Cache\image-thumbs

The cache dir is the right place for anything you'd be willing to lose to a "Clear cache" button.

Recipe 3 — User data dir for non-ephemeral state (database files, downloaded assets the user expects to keep).

python
from pathlib import Path
from platformdirs import user_data_dir

data = Path(user_data_dir("myapp"))
data.mkdir(parents=True, exist_ok=True)
db_path = data / "library.sqlite3"

Output (Linux): /home/alice/.local/share/myapp/library.sqlite3 Output (macOS): /Users/alice/Library/Application Support/myapp/library.sqlite3 Output (Windows): C:\Users\alice\AppData\Local\myapp\library.sqlite3

The macOS data dir collides with the config dir by spec — both live under Application Support. Use sub-directories if you need to keep them separate.

Recipe 4 — Site-wide config (system administrator–edited, all users).

python
from platformdirs import site_config_dir, site_data_dir

print(site_config_dir("myapp"))
print(site_data_dir("myapp"))

Output (Linux): /etc/xdg/myapp and /usr/local/share/myapp Output (macOS): /Library/Application Support/myapp for both Output (Windows): C:\ProgramData\myapp for both

site_* dirs are usually read-only at runtime; write to them only from your installer or a privileged setup step.

Recipe 5 — Per-app subdir convention for multi-binary suites.

python
from platformdirs import PlatformDirs

dirs = PlatformDirs(appname="myapp", appauthor="myorg", version="2", roaming=False)
print(dirs.user_config_path)
print(dirs.user_cache_path)
print(dirs.user_log_path)

Output (Linux): /home/alice/.config/myapp/2, /home/alice/.cache/myapp/2, /home/alice/.local/state/myapp/2/log

PlatformDirs returns a stateful object whose *_path properties yield pathlib.Path instances (the bare functions return strings). version="2" creates a stable subdir useful when a major release reshapes the on-disk layout.

Performance tuning

platformdirs is a sub-microsecond library — the only perf knob is caching the result. The functions read environment variables and run a few os.path.join calls; the cost is invisible unless you call them in a hot loop.

  • Cache the result in a module-level constant. Recomputing user_config_dir("myapp") thousands of times wastes nanoseconds at the OS layer for no gain.
  • PlatformDirs(...) instances are cheap. Building one per call is fine. The class is a thin wrapper around the same lookup logic.
  • roaming=True on Windows adds one env-var lookup, no real cost; set it once and forget.
  • Avoid os.path.expanduser("~") followed by manual join. Each expanduser allocates and is slower than the cached env-var lookup that platformdirs already does.

Version migration guide

platformdirs was forked from appdirs in 2021 and has had three major releases since. The migration surface is small but worth knowing.

  • appdirs → platformdirs 2.x — import path changes only: from appdirs import user_config_dir becomes from platformdirs import user_config_dir. Function names are identical.
  • 2.x → 3.xuser_log_dir and user_state_dir were added. Existing call sites unchanged.
  • 3.x → 4.x — type hints tightened; old version=int callers now need version=str to avoid a TypeError on join.
python
# Before (appdirs)
from appdirs import user_config_dir
cfg = user_config_dir("myapp")

# After (platformdirs)
from platformdirs import user_config_dir
cfg = user_config_dir("myapp")

Output: same path string; no behavior change beyond the import line.

Production deployment notes

  • Always pass appname, optionally appauthor. Without appname, you get the user's home dir back — almost never what you want.
  • Mkdir before writing. platformdirs returns a path; it never creates it. Path(...).mkdir(parents=True, exist_ok=True) is the standard idiom.
  • Containers don't have a "user". Inside a Docker container with no HOME set, platformdirs falls back to /.config/myapp. Set HOME=/root or /home/app in the image and pick a writable mount.
  • Sandboxed macOS apps redirect Application Support into a per-app container. platformdirs honors that — you'll see paths like ~/Library/Containers/com.example.myapp/Data/Library/Application Support/myapp instead of the plain location. This is correct, not a bug.
  • Roaming vs local on Windows. roaming=True returns %APPDATA% (synced across domain machines); the default roaming=False returns %LOCALAPPDATA%. Pick local for caches and large binary state; roaming only for small text settings.

Security considerations

  • Treat returned paths as untrusted before mkdir. Environment-variable injection (XDG_CONFIG_HOME=/etc) could redirect writes. If your app runs setuid or with elevated privileges, validate the path stays under ~.
  • Don't store secrets in user_data_dir alone. The dir is world-readable by default on Linux. Use OS keyrings (keyring package) for credentials; use the data dir only for non-secret state.
  • Cache dir cleanup. Some OSes (macOS Big Sur+) clean ~/Library/Caches/* under disk pressure. Never store data you can't regenerate there.
  • Symlink races. If your app writes to user_config_dir as root, an attacker who controls the user's home can symlink the dir to a privileged location before you write. Drop privileges before touching user paths.

Testing & CI integration

python
import os
from platformdirs import user_config_dir

def test_config_dir(monkeypatch, tmp_path):
    monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
    assert user_config_dir("myapp").startswith(str(tmp_path))

Output: test passes; XDG_CONFIG_HOME overrides the default on Linux.

The package honors XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME, XDG_STATE_HOME, XDG_RUNTIME_DIR on Linux; on macOS and Windows the env vars are ignored unless you use the --xdg variant explicitly. In CI, set these to tmp_path to isolate per-test config and cache directories.

Ecosystem integrations

platformdirs is a transitive dependency of so many tools that you usually already have it installed.

  • pip — pulls it in via virtualenv.
  • poetry — uses it for cache and config locations.
  • black~/.cache/black is sourced from it.
  • virtualenv — for the per-version venv cache.
  • tox — for the work-dir resolution.
  • pylint — for the persistent cache.
  • pre-commit — for the hook cache.

The pattern: any well-behaved Python tool that needs a cache directory pulls in platformdirs.

Troubleshooting common errors

Error / SymptomLikely causeFix
Returned path is ~/myapp only (Linux)No appname passedPass user_config_dir("myapp"), not user_config_dir().
PermissionError on first writeDir doesn't existPath(p).mkdir(parents=True, exist_ok=True) before writing.
Test sees real ~/.configXDG env vars not overriddenmonkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)).
macOS path is under ~/Library/Containers/...App is sandboxedCorrect — that's where macOS isolates sandboxed apps.
Windows path is empty/wrong%APPDATA% not set (rare)Use roaming=False or set the env var.
Tests fail in CI on Linux containerHOME unsetExport HOME in the CI step; XDG fallbacks need it.

When NOT to use this

  • One-off scripts in your own home. Hard-code ~/.myscript and move on; platformdirs is over-engineering for a personal tool.
  • System daemons writing to /var/lib/.... Use the systemd-managed locations (StateDirectory, CacheDirectory) or hard-code; platformdirs is user-scoped.
  • You need OS keyring storage. Use keyring, not platformdirs. The "data dir" is plaintext.
  • You target only one OS. Direct pathlib is simpler and removes a dep. On Linux only, xdg-base-dirs is closer to spec.

Compatibility matrix

Pythonplatformdirs lineNotes
3.72.xDrop floor.
3.83.x, 4.xCurrent minimum for 4.x.
3.94.xFully supported.
3.10+4.xFully supported.
3.134.xFree-threaded build: works (pure Python).

See also