# What Is the Role of AppPreferences.swift in Managing User Settings?

> Discover how AppPreferences.swift manages user settings for OpenSuperWhisper. This singleton provides type-safe access to UserDefaults for seamless setting persistence and retrieval.

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

---

**[`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) serves as the centralized, type-safe singleton that persists and retrieves all user-configurable settings in OpenSuperWhisper through a clean `UserDefaults` API.**

In the OpenSuperWhisper macOS transcription app, the role of [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) in managing user settings is centralized through a single source of truth rather than scattered `UserDefaults` calls. This file defines a singleton hub, located at [`OpenSuperWhisper/Utils/AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Utils/AppPreferences.swift), that wraps Foundation’s preferences system with compile-time type safety and automatic migration logic.

## Singleton Architecture for Global Access

The file exposes one shared instance via `static let shared = AppPreferences()`. This guarantees every module reads the same underlying state without creating multiple `UserDefaults` managers.

Other components access values directly through this singleton. For example, reading the default transcription language looks like this:

```swift
let language = AppPreferences.shared.whisperLanguage   // → "en" by default

```

Updates are equally direct and immediately persist:

```swift
AppPreferences.shared.translateToEnglish = true

```

## Automatic Migration of Legacy Keys

Backward compatibility is handled internally by the `migrateOldPreferences()` method. When the app launches, this method transparently moves legacy keys—such as the older `selectedModelPath`—to the newer `selectedWhisperModelPath` key.

As implemented in `Starmel/OpenSuperWhisper`, this migration ensures existing users retain their configuration after updates without manual intervention.

## Type-Safe Property Wrappers

Instead of raw string keys and manual casting, [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) declares properties through custom wrappers that enforce type safety.

### @UserDefault for Non-Optional Values

The `@UserDefault` wrapper stores non-optional values and supplies a default fallback when the key is absent. This eliminates boilerplate `UserDefaults.standard.object(forKey:)` calls and enforces compile-time correctness.

### @OptionalUserDefault for Optional Values

The `@OptionalUserDefault` wrapper stores optional values, returning `nil` when no value has been persisted. This pattern is used for data that may not exist on first launch, such as microphone calibration:

```swift
let data: Data = … // encoded microphone data
AppPreferences.shared.selectedMicrophoneData = data   // stored as optional

```

## Domain-Specific Preference Grouping

Preferences inside [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) are logically grouped by functional domain. The file organizes properties for engine selection, model paths, transcription parameters, clipboard behavior, and hotkey configuration.

This grouping makes it easy for developers to discover relevant settings. A UI panel in [`OpenSuperWhisper/Settings.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Settings.swift) writes to these grouped properties, while backend services read only the subsets they require.

## Dynamic Derived Properties

Some properties compute their return value based on the active engine. The `selectedModelPath` property is a derived accessor that abstracts the underlying engine-specific path. When the Whisper engine is active, it delegates to `selectedWhisperModelPath`:

```swift
if let modelPath = AppPreferences.shared.selectedModelPath {
    // Feed `modelPath` into the WhisperEngine initializer
}

```

This layer of indirection keeps caller code decoupled from engine-specific storage keys.

## Integration with the Rest of the App

The singleton is consumed throughout the repository. [`OpenSuperWhisper/TranscriptionService.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/TranscriptionService.swift) reads engine and model preferences to initialize transcription pipelines. [`OpenSuperWhisper/ShortcutManager.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/ShortcutManager.swift) retrieves hotkey configuration through the same shared instance:

```swift
let modifier = ModifierKey(rawValue: AppPreferences.shared.modifierOnlyHotkey) ?? .none
let mouseBtn = MouseButton(rawValue: AppPreferences.shared.mouseButtonHotkey) ?? .none

```

Even the app lifecycle layer in [`OpenSuperWhisper/OpenSuperWhisperApp.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/OpenSuperWhisperApp.swift) persists onboarding completion flags via `AppPreferences.shared`, confirming that every layer of the app relies on this single settings backbone.

## Summary

- [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) provides a **singleton** (`AppPreferences.shared`) that acts as the sole source of truth for user settings in OpenSuperWhisper.
- The `migrateOldPreferences()` method handles **legacy key migration** automatically.
- Custom **property wrappers** (`@UserDefault` and `@OptionalUserDefault`) enforce compile-time type safety and remove `UserDefaults` boilerplate.
- Settings are **grouped by domain**, making the API discoverable for UI and service layers.
- **Derived properties** like `selectedModelPath` abstract engine-specific storage behind a unified interface.

## Frequently Asked Questions

### How does AppPreferences.swift persist data across app launches?

It writes every property to `UserDefaults` through custom property wrappers. Because `UserDefaults` is backed by a plist on macOS, values survive app restarts and are available immediately on the next launch via `AppPreferences.shared`.

### What is the difference between @UserDefault and @OptionalUserDefault?

`@UserDefault` is designed for non-optional values and guarantees a fallback default, while `@OptionalUserDefault` stores optional values and returns `nil` when the key has never been set. Both wrappers handle the underlying `UserDefaults` read and write logic automatically.

### Which OpenSuperWhisper components depend on AppPreferences.shared?

Core modules including [`OpenSuperWhisper/TranscriptionService.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/TranscriptionService.swift), [`OpenSuperWhisper/ShortcutManager.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/ShortcutManager.swift), and [`OpenSuperWhisper/Settings.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/Settings.swift) all read or write values through `AppPreferences.shared`. This shared access guarantees a consistent configuration state across the entire application.

### How does the app handle old preference keys after updates?

The `migrateOldPreferences()` method inside [`AppPreferences.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/AppPreferences.swift) transparently remaps legacy keys. For example, it migrates the older `selectedModelPath` to the newer `selectedWhisperModelPath`, so existing users retain their settings without manual migration steps.