View and set default application associations on macOS — file extensions, UTIs and URL schemes. A modern replacement for RCDefaultApp, SwiftDefaultApps and duti, all of which are unmaintained and built on APIs Apple has since deprecated or removed.
Ships as a SwiftUI app and a CLI (mda) over the same core library. Built exclusively on the supported APIs (NSWorkspace, UniformTypeIdentifiers) — no deprecated LS* calls, no private API.
Requires macOS 15+.
- Browse types by family (text, code, images, …) or flip to a per-app view showing everything an app handles — including who currently owns each type
- Change a handler from the dropdown in each row, or pick any app via "Other…"
- Filter across extensions, UTIs and app names (⌘F)
- Localized: English, Deutsch, Français, Español, Italiano, Português, Nederlands, Polski
mda get md # default handler for .md
mda ls md # all candidate handlers, default marked *
mda set com.sublimetext.4 md # set handler for an extension
mda set com.apple.Safari public.html
mda set com.apple.Mail mailto:
mda dump # all curated types with current handlers (or --json)
mda save # snapshot current handlers to ~/.mda/default
mda apply # restore the default preset
mda apply work # or a named one (~/.mda/work)
mda apply settings.duti # or any settings file, duti-compatiblemda save writes the current associations as a plain settings file to ~/.mda/ — one file per preset, human-readable, duti-syntax. Put the folder in your dotfiles (mda save warns when it's not under git, because unversioned saves overwrite without history). mda apply restores a preset: everything that can be applied is applied, apps that aren't installed are skipped and listed, and a non-zero exit tells your bootstrap script there's leftovers — re-run after installing the missing apps, it's idempotent.
The app has the same feature under Presets in the toolbar, with a preview of what would change before anything is written.
Targets are classified automatically: md / .md is an extension, public.html a UTI, mailto: a URL scheme.
mda apply reads duti settings files as-is: three-field lines (bundle-id uti role) work unchanged — the role column is accepted and ignored, because the modern API has no role concept and always sets the all-roles default. Two bare fields mean a URL scheme, exactly like duti -s.
Homebrew:
brew tap klabast/tap
brew install mda # cli
brew install --cask macosdefaultapps # appOr grab the app from the latest release, or build from source:
swift build -c release # cli: .build/release/mda
scripts/package-app.sh # app: build/macOSDefaultApps.appThe app is not notarized yet — if macOS blocks the first launch: System Settings → Privacy & Security → "Open Anyway". The Homebrew-built CLI has no such friction.
- Changing the default browser (
http:/https:) triggers a macOS consent dialog by design; it cannot be silenced. - macOS has no API to remove an association — you can only point it at another app. Stale associations are cleaned up by LaunchServices itself.
- The extension
tsmaps to MPEG-2 Transport Stream system-wide (not TypeScript); the app shows whatever LaunchServices reports.
MIT
