// OPENBOX DOCS

Configuration

Configure environment values, local state, and application settings.

OpenBoxGL reads configuration from three places, in order: the process environment, discovered .env files, and persisted application settings (the Settings dialog). This page documents each source, the exact variables, and the validation limits applied when settings are saved.

Configuration sources

SourceWhen it is readPrecedence
Process environmentAt startup and on demand (os.environ)Highest
Discovered .env filesbootstrap_env at Web UI startup, lazily before some lookupsMiddle; never overrides an existing environment value
Persisted settingslibrary.json -> settingsLowest for credentials; the only store for UI settings

A .env file sets an environment variable only when that variable is not already set, so the process environment always wins. Unreadable or non-UTF-8 .env files are skipped silently; an optional .env must never abort startup.

.env discovery order

env_config.discover_env_files checks these roots in order and loads every .env found:

  1. An explicit OPENBOX_ENV_FILE path, if set (read before the extra roots).
  2. The data directory (the extra root passed to bootstrap_env).
  3. The parent of the data directory.
  4. Your home directory (~/.env).
  5. ~/.config/openbox-game-launcher/.env.

The current working directory and the application directory are not searched. Each .env file must be an owner-only regular file (mode 0o600, no group or other permission bits), must not be a symlink, and must be under 1 MiB; anything else is skipped silently. Windows has no POSIX mode bits, so there the check rejects reparse points instead and otherwise relies on the per-user profile ACL — keep .env inside your own profile directory.

Values already in the environment are never overridden by .env. The template lives at .env.example in the repository. Put real secrets in ~/.env or ~/.config/openbox-game-launcher/.env only, never in a tracked file.

Environment variables

Data and process behavior

VariableMeaning
OPENBOX_DATA_DIRData directory. Read at import time, before .env bootstrap; must be exported in the shell, desktop entry, or systemd unit before launch. Defaults to ~/.local/share/openbox-game-launcher, and to %LOCALAPPDATA%\openbox-game-launcher on Windows.
OPENBOX_SAFE_MODEAny non-empty value (conventionally 1) disables plugin execution and the webhook dispatcher for the whole process. Exposed as settings.safe_mode.
APPIMAGESet automatically when running from an AppImage; the Linux updater refuses to install without it. Exposed as settings.appimage. Windows updates use the verified OpenBox-<arch>-windows.zip path instead.
OPENBOX_ENV_FILEExplicit path to a single .env file, checked first before the data-directory roots. Read directly from the process environment; must point at an owner-only regular file (not a symlink) or it is skipped.
OPENBOX_ALLOW_HTTP_WEBHOOKSSet to 1 to allow plain-HTTP webhook URLs. Required only for trusted local test targets; HTTPS is the default and safer.
OPENBOX_ALLOW_HTTP_GAMEYFINSet to 1 to allow plain-HTTP Gameyfin URLs. HTTPS is the default; loopback (localhost) addresses are always allowed.
OPENBOX_ALLOW_UNSANDBOXED_PLUGINSSet to 1 in the process shell to allow unsandboxed plugin execution when bubblewrap is unavailable. Read directly from the process environment (not .env; it is not in ENV_KEYS).
OPENBOX_MEDIA_ROOTSColon-separated list (os.pathsep) of additional absolute directories approved for scanning and media storage. Up to 32 roots.
OPENBOX_EXECUTABLEOptional explicit path to the emulator/launcher executable recorded when importing Steam titles for Steam Bridge. Read from the process environment at scan time; when unset, the importer uses the Steam-reported executable.
OPENBOX_ENABLE_DMABUFSet to 1 to enable WebKitGTK DMA-BUF rendering in the Linux native window. Disabled by default to prevent silent blank windows on AMD GPUs (including Steam Deck). Windows uses WebView2 instead and ignores this.
OPENBOX_WEBKIT_HARDWARE_ACCELERATIONWebKitGTK hardware acceleration policy in the Linux native window (always or on-demand; default is on-demand).
OPENBOX_SNAPSHOT_DEBOUNCEDebounce delay in seconds (float) for background library state snapshot writes (defaults to 0.0).
OPENBOX_INSTALL_DIRCustom installation directory used by install.sh (defaults to ~/.local/bin). On Windows the same variable, or install.ps1 -InstallDir, defaults to %LOCALAPPDATA%\OpenBox; the runtime lands in <InstallDir>\share\openbox and the previous tree is kept at <InstallDir>\share\openbox.previous.
OPENBOX_RELEASE_TAGPins a specific GitHub release tag (e.g. v1.13.0) during install.sh execution.
OPENBOX_PYTHONPath to the Python interpreter invoked by the native host (defaults to python3; on Windows the launchers locate python.exe or py.exe on PATH unless this is set).
OPENBOX_WEB_APPPath to web_app.py invoked by the native host.
OPENBOX_NATIVE_HOSTPath override for the native host binary used by openbox-native.sh (defaults to native_host beside the app). On Windows it overrides the native_host.exe binary that openbox-native.ps1 runs.
OPENBOX_SHARERuntime directory override used by the Windows launchers (openbox.ps1, openbox-native.ps1) to point at the extracted runtime tree.
OPENBOX_BUNDLED_LIB_PATHLD_LIBRARY_PATH value used by the AppImage wrapper for bundled libraries.
OPENBOX_ARCHOverrides uname -m architecture detection in install.sh and build_appimage.sh (x86_64 or aarch64).
OPENBOX_UPDATE_INFORMATIONzsync update-metadata line embedded by build_appimage.sh for the verified updater (packaging-time, not user-facing).
OPENBOX_RELEASE_BASEBase URL the release assets are served from, read by scripts/install.ps1 (or pass -ReleaseBase). Overrides the default GitHub release download host.
OPENBOX_ENABLE_SQLITE_READSet to 1 to enable the SQLite read model (pkg/state/sqlite_readmodel.py) for accelerated search and facets on large libraries. It uses stdlib sqlite3 with FTS5 full-text search (LIKE fallback if FTS5 is unavailable). JSON remains the source of truth; SQLite is a read-only projection. Release evidence covers 10k and 20k libraries; larger collections are exploratory. Since v1.12.0 the read model also self-enables at 5,000+ games (should_auto_enable(), latched per process); an explicit 0/false/no opt-out is never overridden. (v1.7.2+)

Windows launchers

The Windows runtime tree ships three entry points:

Entry pointWhat it does
openbox.cmdDouble-clickable wrapper
openbox.ps1Runs the ladder: the native WebView2 window first, the browser app window second. --web forces the browser app window.
openbox-native.ps1Runs native_host.exe, or falls back when it is absent

Python is located as python.exe or py.exe on PATH, or through OPENBOX_PYTHON; OPENBOX_SHARE points the launchers at the runtime directory. native_host.exe ships compiled in the released portable install; rebuilding it from a source checkout is powershell -File scripts/build_native_host_windows.ps1 (MSVC toolchain; WebView2 SDK from the NuGet cache or nuget.org), and without native_host.exe the launchers open the same UI in a browser app window. See Windows for the install layout.

Credentials (all optional)

VariableUsed byAliases
RETROACHIEVEMENTS_USERNAMERetroAchievements matching and progressRA_USERNAME, OPENBOX_RA_USERNAME
RETROACHIEVEMENTS_API_KEYRetroAchievements web APIRA_API_KEY, RETROACHIEVEMENTS_KEY, OPENBOX_RA_API_KEY
EMUMOVIES_USERNAMEEmuMovies media downloadsOPENBOX_EMUMOVIES_USERNAME
EMUMOVIES_PASSWORDEmuMovies media downloadsOPENBOX_EMUMOVIES_PASSWORD
GITHUB_TOKENGitHub release API rate limit for update checks (GITHUB_TOKEN, GH_TOKEN, OPENBOX_GITHUB_TOKEN all accepted)GH_TOKEN, OPENBOX_GITHUB_TOKEN
IGDB_CLIENT_IDIGDB metadata provider (Twitch developer app),
IGDB_CLIENT_SECRETIGDB metadata provider (Twitch developer app),
STEAMGRIDDB_API_KEYSteamGridDB artwork provider: search, apply, and bulk matchingOPENBOX_STEAMGRIDDB_API_KEY
SCREENSCRAPER_USERScreenScraper per-ROM-hash scraping (required for that provider)OPENBOX_SCREENSCRAPER_USER
SCREENSCRAPER_PASSWORDScreenScraper per-ROM-hash scraping (required for that provider)OPENBOX_SCREENSCRAPER_PASSWORD
SCREENSCRAPER_DEV_IDScreenScraper developer credentials (optional)OPENBOX_SCREENSCRAPER_DEV_ID
SCREENSCRAPER_DEV_PASSWORDScreenScraper developer credentials (optional)OPENBOX_SCREENSCRAPER_DEV_PASSWORD

Each credential lookup checks the aliases in order using env_value() from env_config.py, it iterates through the listed names for a variable and uses the first non-empty value found. If an empty string is returned for any required variable, the route returns a specific 400 error naming exactly which variable is missing. For example, IGDB requires both IGDB_CLIENT_ID and IGDB_CLIENT_SECRET; without either the IGDB routes return 400 {"error":"Set IGDB_CLIENT_ID and IGDB_CLIENT_SECRET in ~/.env to use IGDB."}. Without RetroAchievements credentials, the /api/ra/* routes return 400 {"error":"Configure RetroAchievements first."}.

Credentials supplied through the Settings dialog are persisted in the data directory (retroachievements.json, emumovies.json, settings.json for Gameyfin) with owner-only permissions (0o600 via secure_text_write). The API and diagnostic log redact the values, but the files themselves are plaintext. Windows has no POSIX mode bits, so there these files rely on the per-user profile ACL instead.

Persisted settings

The Settings dialog saves into library.json under settings. The save handler (SettingsHandlers._save_settings_locked in handlers/settings.py:646) validates each field before committing; an invalid value aborts the whole save with a 400 error. Keys must exist in the settings_schema.py registry: unknown keys are dropped with a diagnostic log warning rather than persisted. GET /api/settings returns the public projection of the settings, which is a curated subset of the registry (a few internal and separately-owned keys are not part of it). Notable validated limits:

SettingDefaultValidation
watch_folders[]List of at most 50 absolute, existing directories; duplicates removed
screensaver_seconds900 (off) or between 30 and 3600; values 1-29 are rejected
controller_map{}Actions limited to play, back, favorite, random, page_left, page_right, pause, menu; button numbers 0-31
gamescope_preset""Selected gamescope preset name (one of: deck, deck_hd, 1080p, 1440p, 4k, integer, stretch, borderless, or a custom preset name). Empty string = no preset. (v1.7.2+)
mangohud_enabledfalseBoolean; when true, MANGOHUD=1 is set on game launch to enable the MangoHud performance overlay. (v1.7.2+)
gamescope_custom_presets[]Up to 16 user-defined gamescope presets (unique names, bounded integer args). A custom name shadows a stock preset; a per-game gamescope_preset override wins over the global choice. (v1.8.0+)
list_sort""List-view sort column key (e.g. title, rating); empty = default order. (v1.8.0+)
list_sort_dir""List-view sort direction: "" (ascending) or "reversed". (v1.8.0+)
show_insightstrueBoolean; toggles the Play Insights panel in the library pane. (v1.8.0+)
locale"en"Interface language code (one of: en, es, de, fr, pt). (v1.7.2+)
progress_automation_enabledfalseBoolean; enables/disables automatic progress changes
progress_automation_play_minutes300 to 100,000, minutes before marking Playing
progress_automation_idle_days300 to 3,650, days before marking Paused
progress_on_first_play"Playing"Must be a known progress status
welcome_completedfalseBoolean, suppresses opening the Library Setup Center on empty library launch
backlog_progress_suggesttrueBoolean; kill switch for the one-time "Playing?" suggestion offered when launching an unplayed game (v1.14.0+)
image_group"cover"One of cover, background, screenshot, clear_logo, fanart, banner, icon, box_back, box_spine, box_3d, title_screen, cart_front, cart_back, disc, advertisement, manual
badge_visibilityfavorite, installed, saves, documents, progress, storefront, achievements, ratingSubset of favorite, installed, missing_media, saves, documents, versions, storefront, achievements, highscores, progress, rating, broken, portable, controller
cloud_folder""Absolute, existing path for mounted-folder statistics sync and optional catalog sync
library_sync_enabledfalseExplicit opt-in for causal catalog synchronization; it does not enable the separate statistics sync automatically
storefront_auto_importAll offObject with boolean keys: steam, heroic, lutris, gameyfin
auto_import_media_types["background", "cover", "screenshots"]Subset of MEDIA_TYPES_ALL (18 types including cover, background, screenshots, clear_logo, box_back, manual, video, etc.)
media_download_limit0 (unlimited)0 to 10,000
region_priority(default list)Non-empty ordered list, ranks which regional media to prefer
video_prioritysnap, theme, trailer, recordingSubset of video_snap, video_theme, video_trailer, video_recording, video
library_music""Path to existing audio file; empty disables Big Box library BGM
video_bgm_mixfalseBoolean, lower music volume when mixing with video audio
bigbox_mode"stage"One of stage, hybrid, coverflow
bigbox_start_at_launchfalseBoolean; when true the UI opens straight into Big Box on startup (or unless a ?deeplink=bigbox deeplink overrides it) (v1.14.0+)
attract_mode_seconds90Seconds of idle before screensaver/attract mode triggers
bigbox_startup_video""Empty string (disabled) or path to startup video file
bigbox_shutdown_commands[]At most 25 commands; run on entering Big Box (see note below)
startup_commands[]At most 25 commands; run after server binds
shutdown_commands[]At most 25 commands; run on graceful exit
track_session_historytrueBoolean, when false, sessions still track but history isn't recorded
backup_on_closefalseBoolean, creates save backups when session ends
save_backup_limit100 to 500, oldest archives trimmed after each backup
backup_auto_enabledfalseBoolean; opt-in weekly automatic library backup, checked on an hourly tick (auto_backup_due()) (v1.12.0+)
backup_auto_keep41 to 52 automatic backup archives retained (v1.12.0+)
last_auto_backup""Internal timestamp of the last automatic backup; missing or unparsable counts as due — do not edit (v1.12.0+)
quick_resume_enabledtrueBoolean; enables Quick Resume state capture and restore on state-capable adapters (v1.11.0+)
session_recap_enabledtrueBoolean; shows the session recap card after a session ends (v1.11.0+)
moments_autocapturetrueBoolean; automatic Moment capture on qualifying session events (v1.11.0+)
state_retention11 to 20 Quick Resume states kept per game (v1.11.0+)
memories_import_enabledfalseBoolean; opt-in import of external media into the Memories gallery (v1.11.0+)
memories_import_roots[]At most 32 absolute, existing directories allowed as Memories import sources (v1.11.0+)
steamgrid_enabledtrueBoolean; enables the SteamGridDB artwork provider (still requires STEAMGRIDDB_API_KEY) (v1.11.0+)
scrape_after_importtrueMaster toggle for the automatic post-import scrape. Owned by the metadata routes rather than the Settings dialog: read/written through GET/POST /api/v2/metadata/scrape-settings
scrape_screenscraper_enabledfalseScreenScraper opt-in for the post-import scrape. Requires SCREENSCRAPER_USER/SCREENSCRAPER_PASSWORD
scrape_igdb_enabledfalseIGDB opt-in for the post-import scrape. Requires IGDB_CLIENT_ID/IGDB_CLIENT_SECRET
scrape_steamgrid_enabledfalseSteamGridDB opt-in for the post-import scrape. Requires STEAMGRIDDB_API_KEY
steamgrid_key_configured(derived)Read-only projection in public_settings: true when a SteamGridDB API key is configured (pkg/state/cache.py:488); not writable via Settings
obs_replay_enabledfalseBoolean; enables Record That clip capture via the OBS replay buffer (v1.11.0+)
obs_websocket_url""OBS WebSocket endpoint for replay-buffer capture (v1.11.0+)
obs_websocket_timeout5.00.1 to 30 seconds for OBS WebSocket calls (v1.11.0+)
obs_websocket_password""OBS WebSocket password; empty leaves the stored value unchanged (v1.11.0+)
museum_kiosk_enabledfalseBoolean; Museum/kiosk mode with reduced interaction (v1.11.0+)
health_rescan"weekly"Library health rescan cadence: daily, weekly, on_startup, or off. on_startup runs once at boot when no game session is active; off disables the hourly tick. (v1.14.0+)
museum_kiosk_pin_hash""Salted PIN hash for the kiosk convenience boundary — not API authentication (v1.11.0+)
mood_match_enabledfalseBoolean; enables adaptive cover theming (Mood Match) from the selected artwork. (v1.9.0+)
mood_match_bigboxfalseBoolean; extends Mood Match to the Big Box background and cover ring. Only effective while mood_match_enabled is true. (v1.9.0+)
household_stats_sharingfalseBoolean; opt-in sharing of play statistics with Household members (v1.11.0+)
party_queue[]Game Night queue of up to 50 unique game ids (v1.9.0+)
party_players22 to 8 Game Night players (v1.9.0+)
party_index0Current position in the Game Night queue (v1.9.0+)
tracking_mode"default"One of default, process, original_process, folder, process_name
tracking_delay00 to 600 seconds before tracking starts after spawn
tracking_frequency2.00.5 to 60 seconds between poll checks
apply_perf"auto"One of off, auto, always, whether TDP limits apply
auto_close_store_clientsfalseBoolean, close Steam/Heroic/Lutris clients after a session ends
obs_auto_attachtrueBoolean, auto-attaches latest OBS recording to game
obs_recording_path""Absolute, existing path override; empty uses discovery
dynamic_play_buttontrueBoolean, shows animated PLAY state
custom_field_defs[]Up to 20 fields; each 50 options max
platform_categoriesBuilt-in mappingPlatform-to-category overrides (Nintendo, Sony, Microsoft, Computer, Arcade, Adventure, Other)
list_columnsDefaultsCapped at 12 columns per platform or global view
library_view"grid"Current persistent view preference
hidden_sidebar_sections[]Capped at 20 entries
tray_enabledfalseBoolean, shows system tray icon
minimize_to_trayfalseBoolean, minimizes to tray instead of closing
show_playlist_actionstrueBoolean, show add/remove playlist buttons
gameyfin_url""Prepends http:// when no scheme present; must resolve
gameyfin_username""Stored as plain text alongside url and password
gameyfin_password""Empty value leaves stored password unchanged; otherwise persisted with 0o600
gameyfin_install_dir""Absolute path; created if missing, rejected if symlink or non-existent. Empty by default; the UI shows a ~/Games/Gameyfin placeholder
gameyfin_provider""Provider label; falls back to first available
ludusavi_backup_path""Optional absolute path for Ludusavi JSON output
cover_grouping"shape"String; shape used to group covers in the library
image_group_by_platform{}Object; per-platform image group overrides
image_group_by_playlist{}Object; per-playlist image group overrides
sidebar_sections["search", "view", "platforms", "playlists", "filters"]List of strings; valid section names: search, view, categories, esrb, platforms, playlists, presets, explorer
platform_documents{}Object; per-platform manual/document lists
filter_presets[]List of objects; named filter rules, each needs at least one rule
import_exclusions[]List of objects with source (steam, heroic, lutris, gameyfin) and external_id
emulator_scan_configs[]List of objects; per-emulator scan configuration
tracking_process_name""String; process name used when tracking_mode is process_name
webhook_attempts31 to 5 delivery retries per webhook
webhook_timeout51 to 15 seconds before delivery times out
webhooks[]At most 32 configs; each needs a URL and at least one event
theme""String; global theme name, empty uses the stock theme
theme_by_platform{}Object; platform to theme name mappings
bigbox_quick(auto-managed)Derived list; presets flagged as Big Box quick actions, capped at 8
controller_prompt_pack"xbox"String; active controller prompt pack (xbox, playstation, nintendo)
controller_prompt_hintfalseBoolean; toggles on-screen controller button prompts in Big Box
active_media_packs[]List of strings; media pack ids, appended when a pack is applied
last_cloud_sync""Internal, auto-managed timestamp; do not edit
last_update_check""Internal, auto-managed timestamp; do not edit
gameyfin_password_setfalseInternal, boolean indicator of whether password is set
gamescope_guestfalseInternal, auto-detected guest mode under Gamescope

Two naming details worth knowing:

  • bigbox_shutdown_commands is misnamed: the commands run when Big Box is entered, not when it is left.
  • attract_mode_seconds is the screensaver delay the Big Box screensaver actually reads; it falls back to screensaver_seconds when unset, and the Settings dialog keeps both fields with the same fallback.

Partial saves merge with existing settings: keys you omit are preserved, and concurrent partial saves of different keys do not lose updates (covered by the API sweep tests). An empty gameyfin_password in a save leaves the stored password unchanged.

Values consumed at startup

These are read once during process startup and require a restart to change:

  • OPENBOX_DATA_DIR (data directory and state store path)
  • server.token / server.port (per-launch, deleted on exit)
  • .env bootstrap (performed once per process)
  • startup_commands (run after the server binds, via run_configured_commands)
  • Profile merging from emulator definition packs (merge_profiles_from_definitions) at startup

shutdown_commands run on graceful exit. Both command lists execute with shlex.split and a new session; failures are logged and skipped, never fatal.

Security notes

  • Public examples must use placeholders. Never commit .env, server.token, retroachievements.json, emumovies.json, or library exports.
  • Keep server.token, provider credentials, and webhook secrets private. The diagnostic log redacts values matching token/password/secret/API-key/authorization patterns, but a .env file is plaintext.
  • Restart OpenBoxGL after changing values consumed at process startup.

The authoritative sources are .env.example, env_config.py, handlers/settings.py, settings_schema.py, and the Settings dialog.