Tabby Configuration Storage and Migration: A Deep Dive into Schema Evolution
Tabby stores all user settings in a single YAML-based configuration file managed by ConfigService, which automatically migrates older schemas to the current version 8 through a series of stepwise transformations at startup.
The open-source terminal emulator Tabby (Eugeny/tabby) centralizes its configuration in a structured YAML file that evolves through explicit schema versions. Understanding how Tabby configuration storage and migration works is essential for developers extending the application or troubleshooting profile issues across updates.
Configuration File Structure and Schema
Top-Level Configuration Keys
The configuration object follows a strict schema defined in tabby-core/src/config.ts. At runtime, ConfigService merges these with platform-specific defaults from tabby-core/src/configDefaults.*.yaml (Linux, macOS, Windows, Web).
version: A number tracking the schema version (currently 8)profiles: Array of connection profiles withid,type(local,ssh,serial),name, optionalgroup(ID),icon,color, and anoptionsobject containing profile-specific settingsgroups: Array of group objects ({ id: string, name: string }) used to organize profilesvault: Object storing encrypted secrets (e.g., stored passwords)encrypted: Boolean flag indicating if the vault requires decryption on loadconfigSync: Object containing remote synchronization settings (host,token, etc.)profileDefaults: Default values applied to new profiles (e.g.,ssh.clearServiceMessagesOnConnect)pluginBlacklist,providerBlacklist: Arrays of disabled plugins/providers consulted byConfigService.enabledServices- Legacy keys (
terminal,ssh,serial): Hold pre-migration data including old connection lists and flags that are transformed into modern profile objects
Platform-Specific Defaults
Tabby merges runtime defaults using ConfigService.mergeDefaults(), loading platform-specific values from tabby-core/src/configDefaults.linux.yaml, configDefaults.macos.yaml, configDefaults.windows.yaml, or configDefaults.web.yaml. These files provide fallback values for any user-undefined settings.
Migration Logic and Version Management
When Tabby starts, ConfigService.migrate() in tabby-core/src/services/config.service.ts performs sequential upgrades. Each migration block checks config.version and transforms the data in-place until reaching version 8.
The stepwise migration pipeline includes:
- Version < 1: Migrates
ssh.privateKey(string) tossh.privateKeys(array) - Version < 2: Introduces top-level
profiles, moves legacy terminal settings, renamessessionOptionstooptions, and assigns new UUID-based IDs - Version < 3: Converts
ssh.recentConnectionsand serial connections into proper profile entries, clears legacylocalStoragecache - Version < 4: Ensures every profile has a valid
id(generating custom UUIDs where missing) - Version < 5: Converts textual profile groups into structured group objects and migrates UI state from
localStorage.profileGroupCollapsed - Version < 6: Moves
ssh.clearServiceMessagesOnConnectintoprofileDefaults - Version < 7: Removes the default sync host (
https://api.tabby.sh) and any associated tokens - Version < 8: Normalizes compression algorithms to array format
['none']
ConfigService Architecture and Data Flow
The ConfigService orchestrates configuration persistence through a five-stage pipeline defined in tabby-core/src/services/config.service.ts:
- Load:
platform.loadConfig()reads the YAML file. If missing, it creates a minimal object{ version: LATEST_VERSION } - Decrypt:
maybeDecryptConfig()handles vault decryption ifencrypted: true, requesting the passphrase from the Vault service - Migrate:
migrate(this._store)upgrades legacy schemas to version 8 through the stepwise transformations - Proxy: A
ConfigProxywraps the store, exposing defaults as transparent properties while tracking user-overridden values separately - Save:
save()serializes the store back to YAML (encrypting if enabled) viaplatform.saveConfig
Programmatic Configuration Access
Developers interact with the configuration system through Angular dependency injection:
// In an Angular component or service (TypeScript)
import { Injectable } from '@angular/core';
import { ConfigService } from 'tabby-core';
@Injectable({ providedIn: 'root' })
export class ProfileManager {
constructor(private config: ConfigService) {}
// Read a setting with fallback to default
get recoverTabs(): boolean {
return this.config.store.recoverTabs ?? false;
}
// Add a new SSH profile programmatically
async addSshProfile() {
const newProfile = {
id: `ssh:${crypto.randomUUID()}`,
type: 'ssh',
name: 'Production Server',
options: {
host: 'prod.example.com',
port: 22,
username: 'admin'
}
};
this.config.store.profiles.push(newProfile);
await this.config.save(); // Persists to YAML (encrypted if enabled)
}
}
The ConfigProxy ensures that reads fall back to platform defaults while writes mark values as user-overridden. Plugins can also contribute defaults by implementing the ConfigProvider interface from tabby-core/src/api/configProvider.ts.
Summary
- Tabby uses a single YAML file for all configuration, managed by
ConfigServiceintabby-core/src/services/config.service.ts - A
versionfield tracks schema compatibility, with automatic migrations upgrading data from older versions to the current version 8 at startup - The system handles encryption transparently through the Vault service when
encrypted: true, decrypting viamaybeDecryptConfig()and re-encrypting onsave() - Platform-specific defaults from
configDefaults.*.yamlfiles merge at runtime viamergeDefaults(), withConfigProxyproviding transparent fallback access - Legacy connection lists from
ssh,serial, andterminalkeys automatically convert to modern profile objects during migration - Changes are persisted by calling
config.save(), which serializes the store (optionally encrypted) back to disk
Frequently Asked Questions
Where does Tabby store its configuration file?
Tabby stores its configuration in a YAML file located in the platform-specific user data directory. The exact path is determined by the platform's loadConfig() implementation—typically %APPDATA%/Tabby/config.yaml on Windows, ~/Library/Application Support/tabby/config.yaml on macOS, and ~/.config/tabby/config.yaml on Linux.
How does Tabby handle configuration changes when updating to a new version?
Tabby automatically migrates configurations through ConfigService.migrate() during application startup. This function checks the version field and applies sequential transformations—such as converting legacy SSH connection lists to modern profile objects—until the configuration reaches the current schema version 8. This process is transparent, preserves user data, and modifies the store in-place before the ConfigProxy wraps it.
Can I encrypt sensitive data in Tabby's configuration?
Yes. Tabby supports encrypting the vault field containing sensitive data like stored passwords. When encryption is enabled, the encrypted flag is set to true, and the maybeDecryptConfig method prompts for the Vault passphrase during the loading phase. The configuration is automatically re-encrypted when saved via config.save().
How do I programmatically access or modify Tabby's configuration from a plugin?
Plugins can inject ConfigService from tabby-core to read and write configuration values. The service exposes a store property wrapped in a ConfigProxy, allowing direct property access that falls back to platform defaults defined in tabby-core/src/configDefaults.*.yaml. Always call await config.save() after modifications to persist changes to the YAML file.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →