Understanding platformdirs¶
Why platformdirs exists¶
Every operating system has its own conventions for where applications should store data, configuration, cache, and logs.
A macOS app puts preferences under ~/Library, a Linux app follows the XDG Base Directory Specification, and a
Windows app uses AppData. Hard-coding any of these paths makes an application non-portable.
platformdirs solves this by detecting the current platform at runtime and returning the correct directory for each
purpose. Application authors write platform-agnostic code while end users get paths that follow their OS conventions.
Choosing the right directory¶
The first question is always: who owns this data? App-internal data — databases, caches, config files, logs — goes in an app dir scoped to your app name. User-facing data — files the user would browse to directly — goes in a media dir that sits alongside their documents, music, and photos.
Within app dirs, the next question is whether the data is essential. If it can be regenerated, use cache (fast
lookups) or runtime (session-only sockets and PIDs). If it is important but not critical, use state (window
positions, recent files). For settings use config; on macOS, preference gives you the separate
~/Library/Preferences location that Apple convention expects. Use data for everything else that must survive app
updates.
Within media dirs, pick the folder that matches the file’s type from the user’s perspective — not what your app does
with it. A font your app installs for the user goes in fonts, not data.
flowchart TD
A([What kind of data?]) --> B{Belongs to the app<br/>or to the user?}
B -- App internal --> C{Can it be deleted<br/>without data loss?}
C -- Yes --> D{Speeds things up?}
D -- Yes --> CACHE[cache dir]
D -- No --> E{Temporary for<br/>this session only?}
E -- Yes --> RUNTIME[runtime dir]
E -- No --> STATE[state dir]
C -- No --> F{What kind?}
F -- Settings / options --> CONFIG[config dir]
F -- macOS preferences --> PREF[preference dir]
F -- Log file --> LOG[log dir]
F -- Everything else --> DATA[data dir]
B -- User-facing file --> G{File type?}
G -- Document / report --> DOC[documents dir]
G -- Downloaded content --> DL[downloads dir]
G -- Image --> PIC[pictures dir]
G -- Video --> VID[videos dir]
G -- Audio --> MUS[music dir]
G -- Font --> FONT[fonts dir]
G -- Template --> TMPL[templates dir]
G -- Project / code --> PROJ[projects dir]
G -- Desktop shortcut --> DESK[desktop dir]
G -- Share with others --> PUB[publicshare dir]
style A fill:#1e40af,stroke:#1e3a8a,color:#fff
style B fill:#d97706,stroke:#b45309,color:#fff
style C fill:#d97706,stroke:#b45309,color:#fff
style D fill:#d97706,stroke:#b45309,color:#fff
style E fill:#d97706,stroke:#b45309,color:#fff
style F fill:#d97706,stroke:#b45309,color:#fff
style G fill:#d97706,stroke:#b45309,color:#fff
style CACHE fill:#2563eb,stroke:#1d4ed8,color:#fff
style RUNTIME fill:#2563eb,stroke:#1d4ed8,color:#fff
style STATE fill:#2563eb,stroke:#1d4ed8,color:#fff
style CONFIG fill:#2563eb,stroke:#1d4ed8,color:#fff
style PREF fill:#7c3aed,stroke:#6d28d9,color:#fff
style LOG fill:#2563eb,stroke:#1d4ed8,color:#fff
style DATA fill:#2563eb,stroke:#1d4ed8,color:#fff
style DOC fill:#16a34a,stroke:#15803d,color:#fff
style DL fill:#16a34a,stroke:#15803d,color:#fff
style PIC fill:#16a34a,stroke:#15803d,color:#fff
style VID fill:#16a34a,stroke:#15803d,color:#fff
style MUS fill:#16a34a,stroke:#15803d,color:#fff
style FONT fill:#16a34a,stroke:#15803d,color:#fff
style TMPL fill:#16a34a,stroke:#15803d,color:#fff
style PROJ fill:#16a34a,stroke:#15803d,color:#fff
style DESK fill:#16a34a,stroke:#15803d,color:#fff
style PUB fill:#16a34a,stroke:#15803d,color:#fff
Data directories¶
Use user_data_dir and site_data_dir for persistent application data that the user expects to keep:
SQLite databases, document stores.
Downloaded files, media assets.
User-created content.
Application state that must survive app updates.
from platformdirs import user_data_path
db_path = user_data_path("MyApp") / "app.db"
downloads_dir = user_data_path("MyApp") / "downloads"
Config directories¶
Use user_config_dir and site_config_dir for configuration files and user preferences:
Settings files (JSON, TOML, INI, YAML).
User preferences and options.
Application themes, keybindings.
Feature flags and toggles.
from platformdirs import user_config_path
import json
config_file = user_config_path("MyApp") / "settings.json"
config_file.parent.mkdir(parents=True, exist_ok=True)
settings = {"theme": "dark", "auto_save": True}
config_file.write_text(json.dumps(settings))
Cache directories¶
Use user_cache_dir and site_cache_dir for regenerable data that improves performance:
API response caches.
Thumbnail images, processed media.
Compiled templates, bytecode.
Downloaded package indexes.
Cached data can be safely deleted without losing functionality. Applications should gracefully handle missing cache directories.
from platformdirs import user_cache_path
cache_dir = user_cache_path("MyApp")
thumbnail_cache = cache_dir / "thumbnails"
api_cache = cache_dir / "api_responses"
State directories¶
Use user_state_dir and site_state_dir for non-critical runtime state:
Window positions, sizes.
Recently opened files, MRU lists.
Undo/redo history.
Search history, autocomplete data.
State persists between sessions but is less important than data or config. Loss of state is inconvenient but not catastrophic.
from platformdirs import user_state_path
import json
state_file = user_state_path("MyApp") / "window_state.json"
state = {"width": 1024, "height": 768, "maximized": False}
state_file.parent.mkdir(parents=True, exist_ok=True)
state_file.write_text(json.dumps(state))
Log directories¶
Use user_log_dir and site_log_dir for application logs:
Debug logs, error logs.
Audit trails, access logs.
Performance metrics.
Crash reports.
import logging
from platformdirs import user_log_path
log_file = user_log_path("MyApp") / "app.log"
log_file.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
User media directories¶
Unlike app dirs (data, config, cache, etc.), media dirs are not scoped to the app name. They point to standard user-facing folders that exist independently of any particular application. Use them when your app needs to read from or save into a folder the user already expects — not when storing application state.
The distinction matters:
user_data_dir("MyApp")→~/.local/share/MyApp— your app’s private storageuser_documents_dir()→~/Documents— the user’s document library
On Linux, media dirs are defined by the XDG user-dirs specification and stored in ~/.config/user-dirs.dirs. The
xdg-user-dirs tool lets users relocate them. Set the corresponding environment variable (XDG_DOCUMENTS_DIR,
XDG_DOWNLOAD_DIR, etc.) to override on a per-session basis. On macOS and Windows, platformdirs returns the
platform-conventional location.
Media and user-facing directories¶
Use these when your app saves or opens files the user should see in their own folders:
user_documents_dir(XDG_DOCUMENTS_DIR)Exported reports, user-authored files. Save here when the file is for the user, not the app.
user_downloads_dir(XDG_DOWNLOAD_DIR)Files fetched from the internet at the user’s request.
user_pictures_dir/user_videos_dir/user_music_dirPlatform media libraries. Use when importing or exporting to the user’s existing collection.
user_desktop_dir(XDG_DESKTOP_DIR)Shortcut files and launchers. Rarely needed directly in code.
user_projects_dir(XDG_PROJECTS_DIR)Root directory for the user’s coding projects. Recently added to xdg-user-dirs.
user_publicshare_dir(XDG_PUBLICSHARE_DIR)Files shared with other local accounts. On Windows this is the machine-wide
C:\Users\Public(%PUBLIC%), not a per-user directory.
from platformdirs import user_documents_path
report = user_documents_path() / "report.pdf"
Do not use these to store application state or config — if the file would confuse the user when they browse the folder,
it belongs in user_data_dir instead.
Templates¶
user_templates_dir (XDG_TEMPLATES_DIR) points to the folder used by file managers for new-file templates. macOS
has no platform-defined templates directory; ~/Templates is returned as a pragmatic fallback.
Fonts¶
user_fonts_dir points to the per-user font installation directory:
Linux:
$XDG_DATA_HOME/fonts(default~/.local/share/fonts) — derived from$XDG_DATA_HOME, not a dedicated env var. See the XDG Base Directory Specification.macOS:
~/Library/FontsWindows:
%LOCALAPPDATA%\Microsoft\Windows\Fonts— the per-user font location added in Windows 10
import shutil
from platformdirs import user_fonts_path
font_dir = user_fonts_path()
font_dir.mkdir(parents=True, exist_ok=True)
shutil.copy("MyFont.ttf", font_dir / "MyFont.ttf")
Preference directory¶
user_preference_dir is meaningful mainly on macOS, where Apple’s conventions distinguish two separate locations:
~/Library/Application Support/AppName— long-term application data, databases, plug-ins~/Library/Preferences/AppName— user-adjustable preference files (historically.plist)
On Linux and Windows, user_preference_dir is an alias for user_config_dir — the XDG and Windows conventions make
no such distinction. On Android, it also aliases user_config_dir.
Use user_preference_dir when you specifically need to follow Apple’s File System Programming Guide
and store preference files in ~/Library/Preferences. For most cross-platform applications user_config_dir is
sufficient.
User vs site directories¶
Each directory type has both user_* and site_* variants serving different purposes.
User directories (user_data_dir, user_config_dir, etc.) are:
Per-user: Each user on the system has their own separate directory.
Writable: Normal users can read and write without special permissions.
Isolated: Changes by one user don’t affect others.
Default choice: Use these unless you specifically need system-wide access.
from platformdirs import user_config_path
# Each user gets their own config
config = user_config_path("MyApp") / "config.json"
Site directories (site_data_dir, site_config_dir, etc.) are:
System-wide: Shared across all users on the machine.
Read-only for users: Typically require administrator/root privileges to write.
System defaults: Store default configurations, shared resources.
Package managers: Used by system package managers for application data.
from platformdirs import site_config_path, user_config_path
# Check site config first (system defaults), then user config (overrides)
site_cfg = site_config_path("MyApp") / "defaults.json"
user_cfg = user_config_path("MyApp") / "config.json"
if user_cfg.exists():
config = user_cfg
elif site_cfg.exists():
config = site_cfg
else:
config = None
Platform conventions¶
Each operating system has its own conventions for where application data belongs. Understanding these conventions helps
explain why platformdirs returns different paths on different platforms.
macOS¶
On macOS, platformdirs uses the standard Apple ~/Library directories by default. See Apple’s File System
Programming Guide
for background.
On macOS, user_data_dir and user_config_dir both resolve to ~/Library/Application Support/AppName. If you
need to separate data from config, use subdirectories within that path.
XDG environment variables (XDG_DATA_HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME, etc.) are also supported and take
precedence over the macOS defaults when set. This allows users who prefer the XDG layout to override the default
behavior.
When Homebrew is installed, site_data_dir and site_cache_dir include the Homebrew prefix as
an additional path when multipath=True.
See platformdirs.macos.MacOS for the full API reference.
Windows¶
On Windows, platformdirs uses the Shell Folder APIs to resolve directories. See Microsoft’s Known Folder
documentation for background.
Key behaviors:
appauthoradds a parent directory:AppData\Local\<Author>\<App>. WhenappauthorisNone(the default), it falls back toappname, producing a doubled path likeAppData\Local\MyApp\MyApp. This follows the Windows convention of grouping applications by publisher. If your application does not need an author directory, passappauthor=Falseto getAppData\Local\MyAppinstead.roaming=Trueswitches fromAppData\LocaltoAppData\Roaming, which syncs across machines in a Windows domain. Use roaming for user preferences that should follow the user; use local (default) for machine-specific data like caches.OPINION:
user_cache_dirappends\Cache,user_log_dirappends\Logs
Unlike Linux/macOS where XDG_* variables are a platform standard, Windows has no built-in convention for overriding
folder locations at the application level. To fill this gap, platformdirs checks WIN_PD_OVERRIDE_* environment
variables before querying the Shell Folder APIs. This is useful when large data (ML models, package caches) should live
on a different drive without changing the system-wide APPDATA / LOCALAPPDATA variables that other applications
rely on.
The override variable name is WIN_PD_OVERRIDE_ followed by the CSIDL suffix:
Environment variable |
Overrides |
|---|---|
|
Roaming user data ( |
|
Local user data, config, cache, state ( |
|
Site-wide data, config, cache, state ( |
|
Documents |
|
Downloads |
|
Pictures |
|
Videos |
|
Music |
|
Desktop |
|
Applications (Start Menu Programs) |
Empty or whitespace-only values are ignored and the normal resolution applies.
Note
Windows Store Python (MSIX)
Python installed from the Microsoft Store runs in a sandboxed (AppContainer) environment. Windows silently redirects
writes under AppData to a per-package private location, e.g.
AppData\Local\Packages\PythonSoftwareFoundation.Python.3.X_<hash>\LocalCache\Local\....
platformdirs returns the logical AppData path, which is correct for code running inside the same sandbox.
However, if you pass these paths to external processes (subprocesses, other applications), those processes may not
see files created at the logical path because they run outside the sandbox.
To obtain the real on-disk path for sharing with external processes, call os.path.realpath() on the path
after the file or directory has been created:
import os
import platformdirs
data_dir = platformdirs.user_data_dir(
appname="MyApp", appauthor="Acme", ensure_exists=True
)
real_dir = os.path.realpath(data_dir)
This is a Windows design limitation, not a platformdirs bug. See Microsoft’s MSIX documentation for details on
filesystem virtualization.
See platformdirs.windows.Windows for the full API reference.
Linux / Unix¶
On Linux and other Unix-like systems, platformdirs follows the XDG Base Directory Specification.
XDG environment variables override the defaults:
XDG_DATA_HOME(default~/.local/share)XDG_CONFIG_HOME(default~/.config)XDG_CACHE_HOME(default~/.cache)XDG_STATE_HOME(default~/.local/state)XDG_RUNTIME_DIR(default/run/user/<uid>)XDG_DATA_DIRS(default/usr/local/share:/usr/share)XDG_CONFIG_DIRS(default/etc/xdg)
When multipath=True, site_data_dir and site_config_dir return all paths from the corresponding
XDG_*_DIRS variable, joined by :.
Writing to site_* directories typically requires root privileges. Normal users can only read from these locations.
FreeBSD / OpenBSD / NetBSD: user_runtime_dir falls back to /var/run/user/<uid> or /tmp/runtime-<uid>
when /run/user/<uid> does not exist.
Note
Running as root
When use_site_for_root=True is passed and the process is running as root (uid 0), user_*_dir calls are
redirected to their site_*_dir equivalents. This is useful for system daemons and installers that should write
to system-wide directories rather than /root/.local/.... XDG user environment variables (e.g. XDG_DATA_HOME)
are bypassed when the redirect is active, since they are typically inherited from the calling user via sudo and
would defeat the purpose. The parameter is accepted on all platforms but only has an effect on Unix.
See platformdirs.unix.Unix for the full API reference.
Android¶
On Android, platformdirs uses the app’s private storage directories. The app’s package folder (e.g.
/data/data/com.example.app) is detected via python-for-android or pyjnius. All directories are within your
app’s private storage and data is automatically removed when the app is uninstalled.
Media directories (documents, downloads, pictures, videos, music) point to shared external storage under
/storage/emulated/0/. App-private directories don’t require storage permissions; external storage access requires
appropriate Android permissions.
Shell environments: Android apps like Termux and Pydroid that function as Linux shells are
detected by the presence of the SHELL environment variable. In these environments, platformdirs uses the
Unix/XDG backend instead, including support for XDG_* environment variables.
See platformdirs.android.Android for the full API reference.
Real-world examples¶
These examples show how popular Python projects use platformdirs in production. Each example is based on actual code
with links to the source.
Black (code formatter)¶
Black uses user_cache_path to cache formatted files, speeding up repeat runs:
import os
from pathlib import Path
from platformdirs import user_cache_path
def get_cache_dir() -> Path:
# Allow customization via environment variable
default_cache_dir = user_cache_path("black")
cache_dir = Path(os.environ.get("BLACK_CACHE_DIR", default_cache_dir))
return cache_dir / __version__
CACHE_DIR = get_cache_dir()
See black/cache.py for the full implementation.
virtualenv (environment manager)¶
virtualenv uses user_data_dir to store application data with fallback to
temp directory if not writable:
import os
from platformdirs import user_data_dir
def get_app_data_dir(env):
key = "VIRTUALENV_OVERRIDE_APP_DATA"
if key in env:
return env[key]
return user_data_dir(appname="virtualenv", appauthor="pypa")
folder = get_app_data_dir(os.environ)
folder = os.path.abspath(folder)
# Create directory and check writability
os.makedirs(folder, exist_ok=True)
if not os.access(folder, os.W_OK):
# Fallback to temp directory
folder = tempfile.gettempdir()
See virtualenv/app_data/__init__.py for the full implementation.
Poetry (dependency manager)¶
Poetry uses all three directory types with environment variable overrides:
import os
from pathlib import Path
from platformdirs import user_cache_path, user_config_path, user_data_path
APP_NAME = "pypoetry"
# Cache directory for downloads and build artifacts
DEFAULT_CACHE_DIR = user_cache_path(APP_NAME, appauthor=False)
# Config directory with environment override and roaming enabled
CONFIG_DIR = Path(
os.getenv("POETRY_CONFIG_DIR")
or user_config_path(APP_NAME, appauthor=False, roaming=True)
)
# Data directory with environment override
def data_dir() -> Path:
if poetry_home := os.getenv("POETRY_HOME"):
return Path(poetry_home).expanduser()
return user_data_path(APP_NAME, appauthor=False, roaming=True)
See poetry/locations.py for the full implementation.
tox (testing tool)¶
tox uses user_config_dir for user-level configuration:
from pathlib import Path
from platformdirs import user_config_dir
DEFAULT_CONFIG_FILE = Path(user_config_dir("tox")) / "config.ini"
# Load config with environment variable override
config_file = os.getenv("TOX_USER_CONFIG_FILE", DEFAULT_CONFIG_FILE)
See tox/config/cli/ini.py for the full implementation.