docs/references/data/boot-config-overview.md
The Boot Config system provides synchronous, file-based configuration for settings that must be available before the application lifecycle takes over — before the database, before PreferenceService, before any lifecycle phase runs.
BootConfigService handles data that:
BeforeReady phaseTypical examples: disabling hardware acceleration, setting user data directory paths, configuring Chromium command-line switches.
┌──────────────────────────────────────────────────────────────────────┐
│ App Startup Sequence │
│ │
│ 1. BootConfig load ← Sync read of boot-config.json │
│ (bootConfigService) Only data system available here │
│ │ │
│ 2. Bootstrap ← App data directory setup │
│ │ │
│ 3. application.bootstrap() │
│ │ │
│ ├── Background phase (fire-and-forget) │
│ │ │
│ ├── Promise.all([ │
│ │ BeforeReady phase, ← DB init, PreferenceService, │
│ │ app.whenReady() CacheService, DataApiService │
│ │ ]) │
│ │ │
│ └── WhenReady phase ← Window creation, IPC handlers │
│ │
└──────────────────────────────────────────────────────────────────────┘
BootConfig is the only data system available at stage 1 — before the lifecycle system takes over. The BeforeReady phase and app.whenReady() run in parallel (via Promise.all); once both complete, WhenReady services start. From the BeforeReady phase onward, public boot config values are also accessible through PreferenceService via the BootConfig.* prefix. Internal temp.* keys are the exception — they are never exposed through PreferenceService (see Internal temp.* namespace).
boot-config.json via fs.readFileSync on module importsrc/main/main.tsKeys follow the same naming convention as preferences: namespace.key_name
| Key | Type | Default | Description |
|---|---|---|---|
app.disable_hardware_acceleration | boolean | false | Disable Chromium hardware acceleration |
boot-config.jsonset() marks state dirty and schedules a debounced background save (350ms) to coalesce rapid changes. Background saves are best-effort: a write failure is logged, not thrown, and the dirty flag is kept for a later retry.flush() — force an immediate write, best-effort (never throws; logs and swallows failures). Use only where a failed write is genuinely tolerable, e.g. app quit.persist() — force an immediate write, strict (throws on any fs failure; dirty flag retained on failure for retry). Use wherever a failed write has consequences — BootConfigMigrator, the preboot userData pin (pinUserDataPath, whose silent failure would loop the next launch), or an IPC handler that must not report success before the change is on disk. Choose strict vs. best-effort by the consequence of failure, not by "it runs in preboot" — a preboot caller that needs durability uses persist() and routes the throw to an explicit fatal path, rather than downgrading to flush().parse_error (invalid JSON), read_error (file inaccessible), or validation_error (valid JSON with values failing the zod schema)DefaultBootConfig — this is normalparse_error / read_error): records the error via loadError; all values fall back to defaultsvalidation_error): every present key is validated against bootConfigSchema on load; invalid keys fall back to defaults (listed in loadError.invalidKeys) while valid keys are kept. A non-object file root is also a validation_errorApplication.bootstrap() checks hasLoadError() before any services start and shows a recovery dialog. Validation errors offer "Repair and Restart" (repair() — rewrite the file keeping the valid keys); parse errors offer "Reset and Restart" (reset() — delete the file); read errors offer plain "Restart"hasLoadError() / getLoadError() / clearLoadError()set() throws on values failing the schema before any state change — the single runtime enforcement point covering typed callers, the Preference IPC route, and BootConfigMigrator (which skips invalid v1 values at prepare time)┌─────────────────────────────────────────────────────────────────┐
│ Early Boot (before lifecycle) │
│ │
│ src/main/main.ts │
│ │ │
│ ▼ │
│ bootConfigService.get('app.disable_hardware_acceleration') │
│ │ ▲ │
│ ▼ │ │
│ ┌───────────────────┴──────────────────┐ │
│ │ BootConfigService │ │
│ │ - Sync load on import │ │
│ │ - In-memory config map │◄──── boot-config.json │
│ │ - Debounced save │ (~/.cherrystudio/)│
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ After Lifecycle Starts │
│ │
│ Renderer Main Process │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ usePreference │ IPC │ PreferenceService │ │
│ │ ('BootConfig.*') │─────────────►│ detects BootConfig.* │ │
│ └──────────────────┘ │ prefix, routes to │ │
│ │ bootConfigService │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
BootConfig also carries data migrated from v1's ~/.cherrystudio/config/config.json file (see BootConfigMigrator's file source). The app.user_data_path key holds the custom user data directory mapping that the v1 file stored under appDataPath; preboot reads it before the path registry is frozen. User-initiated directory changes are first written to temp.user_data_relocation, then the next launch executes them during preboot (src/main/services/userDataRelocation/), copying or switching the Electron userData directory and committing app.user_data_path.
| Context | API | Note |
|---|---|---|
| Early boot (before lifecycle) | bootConfigService.get(key) / .set(key, value) | Only option — DB and lifecycle not yet available |
| Lifecycle services (Main) | preferenceService.get('BootConfig.*') | Standardized; enables cross-window sync |
| Renderer (React components) | usePreference('BootConfig.*') | Same as regular preference usage |
Internal temp.* keys (any phase) | bootConfigService.get/set / .onChange() | Never exposed via PreferenceService — see below |
Rule: Once the lifecycle is running, always access public boot config values through PreferenceService. Direct bootConfigService usage is reserved for two cases: early boot code, and the internal temp.* namespace (below).
The userData relocation request writes the internal temp.user_data_relocation key directly through bootConfigService, then calls persist() before relaunch so a failed write rejects the request.
For detailed usage of usePreference and preferenceService, see Preference Usage Guide.
temp.* namespaceBoot config keys under the temp.* prefix are main-process-internal transient state — single in-flight operations meant to be cleared once consumed (e.g. temp.user_data_relocation). They are deliberately excluded from the unified preference API:
UnifiedPreferenceType; not reachable via preload or usePreference.get / set / getMultipleRaw / setMultiple / subscribe), and filtered out of getAll().Restoring a stale temp.* entry (via backup, sync, or a different machine) can cause silent data corruption, so these keys are never backed up or synced. Owning main-process modules must use bootConfigService directly — at any phase, not only early boot — and bootConfigService.onChange() for in-process change notification.
| Aspect | BootConfig | Preference |
|---|---|---|
| Loading | Synchronous, before lifecycle takes over | Async, at BeforeReady phase (parallel with app.whenReady()) |
| Storage | boot-config.json (filesystem) | SQLite database |
| Availability | From process start | After DB initialization |
| Use case | Process-level flags, Chromium switches | User-modifiable app settings |
| Cross-window sync | Via PreferenceService delegation | Native |
| Key count | Minimal (process-level only) | 158+ keys |
Boot config keys are accessible through PreferenceService using the BootConfig. prefix:
preferenceService.get('BootConfig.app.disable_hardware_acceleration') routes to bootConfigService.get('app.disable_hardware_acceleration')BootConfigPreferenceKeys mapped type automatically adds the BootConfig. prefix to all public boot config keys — internal temp.* keys (InternalBootConfigKey) are excludedUnifiedPreferenceType merges preference and public boot config type spaces, providing full type safetyUtility functions in src/shared/data/preference/preferenceUtils.ts:
| Function | Purpose |
|---|---|
isBootConfigKey(key) | Check if a key has the BootConfig. prefix |
isPublicBootConfigKey(key) | Whitelist guard — true only for a BootConfig. key that is public (not internal) |
toBootConfigKey(key) | Strip BootConfig. prefix to get the underlying key |
getDefaultValue(key) | Unified default lookup for both preference and boot config keys |
~/.cherrystudio/boot-config.json (intentionally outside userData)Why outside
userData? Boot config must be readable before the app data directory is determined. Storing it underuserDatawould create a chicken-and-egg problem: the file that decides where data lives cannot itself live inside that data. Placing it under~/.cherrystudio/keeps it stable across changes toappDataPathand ensures it is always available at process start, beforeinitAppDataDir()runs.
{
"app.disable_hardware_acceleration": false,
"app.user_data_path": {
"/Applications/Cherry Studio.app/Contents/MacOS/Cherry Studio": "/Volumes/External/CherryData"
}
}
app.user_data_path is a Record<executablePath, dataPath> keyed by the executable path — same-machine multiple installations (stable / dev / portable) can each have their own user data directory, matching the semantic of v1's appDataPath array.
| File | Purpose |
|---|---|
src/main/data/bootConfig/BootConfigService.ts | Core service — sync load, debounced save, change notifications |
src/main/data/bootConfig/types.ts | BootConfigLoadError type definition |
src/shared/data/bootConfig/bootConfigSchemas.ts | bootConfigSchema (zod, single source), inferred BootConfigSchema type, DefaultBootConfig |
src/shared/data/bootConfig/bootConfigTypes.ts | BootConfigKey, Public/InternalBootConfigKey, BootConfigPreferenceKeys mapped type |
src/shared/data/preference/preferenceUtils.ts | BootConfig.* prefix routing + isPublicBootConfigKey whitelist guard |
src/main/data/PreferenceService.ts | Routes BootConfig.* keys to bootConfigService |
src/main/main.ts | Early boot usage (first import, hardware acceleration check) |
usePreference hook and service API