# How to Access or Modify Application Preferences Programmatically in OpenSuperWhisper

> Learn to access and modify OpenSuperWhisper application preferences programmatically with AppPreferences.shared and read/write to UserDefaults instantly.

- Repository: [Starmel/OpenSuperWhisper](https://github.com/Starmel/OpenSuperWhisper)
- Tags: how-to-guide
- Published: 2026-07-05

---

**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`](https://github.com/Starmel/OpenSuperWhisper/blob/main/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<T>** — Persists a non-optional value to `UserDefaults.standard` and returns a specified `defaultValue` when the key is missing.
- **OptionalUserDefault<T>** — Persists an optional value, where `nil` removes the key from `UserDefaults`.

For example, the transcription language is declared as:

```swift
@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`:

```swift
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:

```swift
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>`:

```swift
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`](https://github.com/Starmel/OpenSuperWhisper/blob/main/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`.

```swift
// 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`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/SettingsViewModel.swift). The view model mirrors `AppPreferences` into `@Published` properties and writes back on every change:

```swift
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:

- [`OpenSuperWhisper/Utils/AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Utils/AppPreferences.swift) — Defines the singleton, property wrappers, and default-value migration.
- [`OpenSuperWhisper/Settings.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Settings.swift) — Exposes a plain Swift struct snapshot for non-UI consumers that do not need observation.
- [`OpenSuperWhisper/SettingsViewModel.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/SettingsViewModel.swift) — Bridges the singleton to SwiftUI via `@Published` properties.

## Summary

- `AppPreferences.shared` in [`OpenSuperWhisper/Utils/AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Utils/AppPreferences.swift) is the single entry point for every setting.
- **UserDefault<T>** wraps non-optional keys with a guaranteed fallback, while **OptionalUserDefault<T>** 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`](https://github.com/Starmel/OpenSuperWhisper/blob/main/Settings.swift) and [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift)?

[`OpenSuperWhisper/Utils/AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Utils/AppPreferences.swift) owns the persistent singleton and the property-wrapper definitions, whereas [`OpenSuperWhisper/Settings.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Settings.swift) provides a plain struct snapshot for non-UI code that only needs a one-time read of the current configuration.