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 storage

  • user_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_dir

Platform 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/Fonts

  • Windows: %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:

  • appauthor adds a parent directory: AppData\Local\<Author>\<App>. When appauthor is None (the default), it falls back to appname, producing a doubled path like AppData\Local\MyApp\MyApp. This follows the Windows convention of grouping applications by publisher. If your application does not need an author directory, pass appauthor=False to get AppData\Local\MyApp instead.

  • roaming=True switches from AppData\Local to AppData\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_dir appends \Cache, user_log_dir appends \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

WIN_PD_OVERRIDE_APPDATA

Roaming user data (AppData\Roaming)

WIN_PD_OVERRIDE_LOCAL_APPDATA

Local user data, config, cache, state (AppData\Local)

WIN_PD_OVERRIDE_COMMON_APPDATA

Site-wide data, config, cache, state (ProgramData)

WIN_PD_OVERRIDE_PERSONAL

Documents

WIN_PD_OVERRIDE_DOWNLOADS

Downloads

WIN_PD_OVERRIDE_MYPICTURES

Pictures

WIN_PD_OVERRIDE_MYVIDEO

Videos

WIN_PD_OVERRIDE_MYMUSIC

Music

WIN_PD_OVERRIDE_DESKTOPDIRECTORY

Desktop

WIN_PD_OVERRIDE_PROGRAMS

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.