Skip to content

Settings

Verql settings follow VS Code’s model: changes auto-apply (no draft/save split) and persist immediately. Every setting is wired end-to-end — UI control → renderer store → IPC → on-disk ConfigStore → broadcast back → consumed somewhere it takes effect. This doc maps that pipeline and where each setting lands.

flowchart LR
  UI["Settings UI<br/>components/settings/categories/*"] -->|set keyPath, value| Store["useSettingsStore<br/>stores/settings.ts"]
  Store -->|optimistic update| UI
  Store -->|invoke settings:set| Handler["ipc/settings.ts"]
  Handler -->|setSetting| Config["ConfigStore<br/>config/store.ts (atomic JSON)"]
  Handler -->|webContents.send settings:changed| Store
  Config -->|secrets stripped → keyring| Keyring["keyring.ts"]
  Store -->|selectors| Consumers["consumers:<br/>editor options, result formatting,<br/>query history, tab restore, keybindings, i18n"]
  • Auto-apply: each control calls useSettingsStore.set(keyPath, value), which optimistically updates the in-memory mirror and fires settings:set.
  • Broadcast: the main process echoes settings:changed so any other window stays in sync (initSettingsListener).
  • Hydrate: on boot the store loads settings:get-all and runs mergeWithDefaults so new keys pick up defaults automatically.
PieceFileRole
Shape + defaultsshared/settings.tsAppSettings, defaultSettings, mergeWithDefaults, and the KEYBINDING_ACTION id registry
Renderer mirrorsrc/renderer/src/stores/settings.tsuseSettingsStore (set, hydrate, resetCategory) + the change listener
IPC handlerssrc/main/ipc/settings.tssettings:get-all/get/set/reset; routes ai.openaiKey/ai.anthropicKey into the keyring instead of disk
Persistencesrc/main/config/store.tsConfigStore — one atomic JSON file; strips keyring-backed secrets before writing
UIsrc/renderer/src/components/settings/SettingsLayout → category components; categories centralized in lib/settings-categories.ts (SETTINGS_CATEGORY)

Category ids are a single source of truth (SETTINGS_CATEGORY in lib/settings-categories.ts) — the nav, the body dispatch, and the open-settings deep-link all consume it, so opening settings always lands on the correct category.

CategoryKey examplesConsumed by
GeneralqueryTimeout, defaultPageSize, maxHistoryItems, confirmDestructiveQueries, confirmOnUnsavedClose, restoreTabsOnStartup, languageQueryPanel (timeout/confirm), ResultsGrid (page size), query history, tab-actions (close confirm), tab restore, i18n locale
AppearanceappearanceMode, theme/lightTheme/darkTheme, uiDensity, accentColor, animations, sidebar/dock visibility + sizesThemeProvider, App shell layout, usePanelResize
Editorfont, tab size, word wrap, minimap, line numbers, cursor, ligatures, …QueryEditor Monaco options
Data DisplaynullDisplay, dateFormat (+customDateFormat), numberFormat, booleanDisplay, truncateTextAt, maxColumnWidthResultsGrid via lib/format-cell.ts
Keybindingskeybindings[] (built-in action ids)App global shortcuts + Monaco editor, both driven by the array; rebind UI in KeybindingsSettings
Connections(driver-contributed only)per-driver plugin settings; SSL/ports live with the driver
AIollamaEndpoint, activeProvider/activeModel, OpenAI/Anthropic keysAI plugin; keys stored in the keyring, redacted on read
MCPenabled, port, autoPort, readOnly, maxRows, disabledTools, tokenMCP server
Pluginsplugins{}, disabledPlugins[], pluginGrants{}plugin host + per-plugin contributed settings
  • Query history (maxHistoryItems) — runs are recorded to the SQLite app-data query_history table, capped to the preference, surfaced via the Saved/History toggle. See stores/query-history.ts.
  • Tab restore (restoreTabsOnStartup) — open query tabs are snapshotted to localStorage and re-opened on launch. See stores/tab-persistence.ts.
  • Keybinding rebind — the persisted keybindings[] drives both App-level shortcuts (via matchesAccelerator) and the editor; the page captures a chord and writes compatible key strings. Action ids: KEYBINDING_ACTION.
  • Secrets — AI API keys and the MCP token never touch disk; settings:set redirects them to the OS keyring and reads are redacted.
  • Language (general.language) — selects the i18n locale (see i18n.md).
  1. Add the field + default to AppSettings / defaultSettings in shared/settings.ts (it merges into existing configs automatically).
  2. Render a control in the right components/settings/categories/* file, calling setSetting('category.key', value). Use t() for the label/description.
  3. Consume it where it takes effect (a store/component selector). A persisted setting with no consumer is dead — wire it through.
  4. Secrets go through the keyring (mirror the AI-key handling in ipc/settings.ts), never the config JSON.

Plugins declare settings in their manifest; they render under the relevant category via PluginContributedSettings and persist into settings.plugins[<id>] through plugins:set-setting. See plugins.md.