How to Access or Modify Application Preferences Programmatically in OpenSuperWhisper

Accessing or modifying application preferences programmatically in OpenSuperWhisper is done entirely through the AppPreferences.shared singleton, whose custom property wrappers read from and write to UserDefaults immediately on every access.

OpenSuperWhisper stores all user-configurable options in a centralized Swift singleton rather than scattering keys across view models. Because the AppPreferences class in OpenSuperWhisper/Utils/AppPreferences.swift relies on lightweight property wrappers, you can access or modify application preferences programmatically from any module or unit test without importing extra frameworks or handling threading concerns.

The AppPreferences Singleton and Property Wrappers

The heart of the system is AppPreferences, a singleton that exposes a static shared instance and lazily migrates legacy keys on initialization. Each preference is declared as a Swift property wrapped by one of two types defined in the same file:

  • UserDefault — Persists a non-optional value to UserDefaults.standard and returns a specified defaultValue when the key is missing.
  • OptionalUserDefault — Persists an optional value, where nil removes the key from UserDefaults.

For example, the transcription language is declared as:

@UserDefault(key: "whisperLanguage", defaultValue: "en")
var whisperLanguage: String

Reading or assigning to whisperLanguage routes through the wrapper’s wrappedValue, which calls UserDefaults.standard.object(forKey:) on access and set(_:forKey:) on assignment.

Reading a Preference Programmatically

To retrieve a stored setting, read the property directly from AppPreferences.shared:

let currentLanguage = AppPreferences.shared.whisperLanguage
print("Current transcription language: \(currentLanguage)")

If the user has never changed the language, the wrapper returns the default fallback "en".

Modifying a Preference Programmatically

Changing a value is equally direct. Assigning to the singleton property updates the underlying UserDefaults entry instantly:

AppPreferences.shared.autoCopyToClipboard = true
AppPreferences.shared.modifierOnlyHotkey = "leftCommand"

No explicit save call is required; the property wrapper synchronizes the value on each didSet.

Working with Optional Preferences

Some settings have no sensible fallback and are therefore optional. The selectedWhisperModelPath property uses OptionalUserDefault<T>:

if let modelPath = AppPreferences.shared.selectedWhisperModelPath {
    print("Using model at: \(modelPath)")
} else {
    print("No Whisper model selected")
}

// Clear the selection by setting nil
AppPreferences.shared.selectedWhisperModelPath = nil

Storing nil removes the key from UserDefaults entirely.

Adding a New Preference

Extending the settings surface requires only two steps in OpenSuperWhisper/Utils/AppPreferences.swift:

  1. Declare a new wrapped property with a unique key and an appropriate default.
  2. Access the property anywhere in the app through AppPreferences.shared.
// 1. Declaration inside AppPreferences.swift
@UserDefault(key: "useDarkMode", defaultValue: false)
var useDarkMode: Bool

// 2. Programmatic usage
AppPreferences.shared.useDarkMode = true

The wrapper automatically registers the key in UserDefaults and handles serialization for any PropertyList type.

Syncing Preferences with SwiftUI

The Settings UI keeps reactive state in OpenSuperWhisper/SettingsViewModel.swift. The view model mirrors AppPreferences into @Published properties and writes back on every change:

class SettingsViewModel: ObservableObject {
    @Published var debugMode: Bool {
        didSet { AppPreferences.shared.debugMode = debugMode }
    }

    init() {
        self.debugMode = AppPreferences.shared.debugMode
    }
}

This ensures the UserDefaults backing store remains the single source of truth while SwiftUI views stay responsive.

Key Files in the Preferences System

Three files form the complete pipeline from definition to UI:

Summary

  • AppPreferences.shared in OpenSuperWhisper/Utils/AppPreferences.swift is the single entry point for every setting.
  • UserDefault wraps non-optional keys with a guaranteed fallback, while OptionalUserDefault handles nilable settings.
  • You can access or modify application preferences programmatically with standard dot syntax; changes persist to UserDefaults immediately.
  • The SettingsViewModel mirrors these values to keep the SwiftUI interface synchronized without duplicating state.
  • No special APIs, file-path manipulation, or threading logic is required.

Frequently Asked Questions

How do I read a preference if I do not know whether it has been set?

The UserDefault<T> wrapper stores a defaultValue inside its declaration, so reading AppPreferences.shared.whisperLanguage always yields a valid string. You never need to check UserDefaults directly for key existence.

Is it safe to change preferences from a background thread?

Yes. The property wrappers perform simple UserDefaults.standard get and set operations, and UserDefaults is thread-safe on macOS. You can assign values from any queue without additional synchronization.

Where is the data physically stored?

The wrappers delegate to UserDefaults.standard, which persists values in the app’s sandboxed preferences property list. You do not need to manage file paths or plist serialization yourself.

What is the difference between Settings.swift and AppPreferences.swift?

OpenSuperWhisper/Utils/AppPreferences.swift owns the persistent singleton and the property-wrapper definitions, whereas OpenSuperWhisper/Settings.swift provides a plain struct snapshot for non-UI code that only needs a one-time read of the current configuration.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →