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
pip install platformdirs
Output: (none — exits 0 on success)
uv add platformdirs
Output: dependency resolved + added to pyproject.toml
poetry add platformdirs
Output: updated lockfile + virtualenv install
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.xline as of mid-2026. Semantic-versioned and very small surface area — bumps are usually safe. - Supports Python 3.8+ on recent releases;
4.xrequires 3.8 minimum. - The
appdirspredecessor stopped at1.4.4in 2020 and is abandoned.platformdirsis a drop-in replacement from a process standpoint — the function names changed (user_config_dirinstead ofuser_config_dir— yes, identical for that one), but the import path isplatformdirs, notappdirs.
Package metadata
- Maintainer: Bernát Gábor (core) and the
platformdirsGitHub org - Project home: github.com/platformdirs/platformdirs
- Docs: platformdirs.readthedocs.io
- PyPI: pypi.org/project/platformdirs
- License: MIT
- First released: 2021 (as the
appdirsfork) - 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]—mypyplugin metadata; rarely needed because the package ships its ownpy.typedmarker.
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
| Package | Trade-off |
|---|---|
appdirs | Abandoned since 2020. Identical idea, smaller maintenance pulse. Use only if a legacy codebase already imports it. |
xdg-base-dirs | Linux-only, strict XDG-spec implementation. Use when you only target Linux and want zero Windows/macOS noise. |
Manual os.environ + pathlib | Zero deps. Fine for one path, painful for the full five-directory matrix and the macOS sandbox cases. |
click.get_app_dir | Bundled 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).
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.
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).
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).
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.
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=Trueon Windows adds one env-var lookup, no real cost; set it once and forget.- Avoid
os.path.expanduser("~")followed by manual join. Eachexpanduserallocates and is slower than the cached env-var lookup thatplatformdirsalready 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_dirbecomesfrom platformdirs import user_config_dir. Function names are identical.2.x → 3.x—user_log_diranduser_state_dirwere added. Existing call sites unchanged.3.x → 4.x— type hints tightened; oldversion=intcallers now needversion=strto avoid aTypeErroron join.
# 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, optionallyappauthor. Withoutappname, you get the user's home dir back — almost never what you want. - Mkdir before writing.
platformdirsreturns 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
HOMEset,platformdirsfalls back to/.config/myapp. SetHOME=/rootor/home/appin the image and pick a writable mount. - Sandboxed macOS apps redirect Application Support into a per-app container.
platformdirshonors that — you'll see paths like~/Library/Containers/com.example.myapp/Data/Library/Application Support/myappinstead of the plain location. This is correct, not a bug. - Roaming vs local on Windows.
roaming=Truereturns%APPDATA%(synced across domain machines); the defaultroaming=Falsereturns%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 (
keyringpackage) 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_diras 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
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 viavirtualenv.poetry— uses it for cache and config locations.black—~/.cache/blackis 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 / Symptom | Likely cause | Fix |
|---|---|---|
Returned path is ~/myapp only (Linux) | No appname passed | Pass user_config_dir("myapp"), not user_config_dir(). |
PermissionError on first write | Dir doesn't exist | Path(p).mkdir(parents=True, exist_ok=True) before writing. |
Test sees real ~/.config | XDG env vars not overridden | monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)). |
macOS path is under ~/Library/Containers/... | App is sandboxed | Correct — 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 container | HOME unset | Export HOME in the CI step; XDG fallbacks need it. |
When NOT to use this
- One-off scripts in your own home. Hard-code
~/.myscriptand move on;platformdirsis over-engineering for a personal tool. - System daemons writing to
/var/lib/.... Use the systemd-managed locations (StateDirectory,CacheDirectory) or hard-code;platformdirsis user-scoped. - You need OS keyring storage. Use
keyring, notplatformdirs. The "data dir" is plaintext. - You target only one OS. Direct
pathlibis simpler and removes a dep. On Linux only,xdg-base-dirsis closer to spec.
Compatibility matrix
| Python | platformdirs line | Notes |
|---|---|---|
| 3.7 | 2.x | Drop floor. |
| 3.8 | 3.x, 4.x | Current minimum for 4.x. |
| 3.9 | 4.x | Fully supported. |
| 3.10+ | 4.x | Fully supported. |
| 3.13 | 4.x | Free-threaded build: works (pure Python). |
See also
- Concept: filesystem — how OSes lay out user data
- Python: pathlib —
Pathis the natural pair forplatformdirs