Back to articles

Managing Default File Associations with duti

Computer TechSystem ConfigurationmacOS ConfigurationManaging Default File Associations with duti
Updated 4/23/2026

Managing Default File Associations with duti

duti is a macOS command-line tool for setting default applications for file types, URL schemes, and MIME types. It's managed by Homebrew and configured via chezmoi for consistent cross-machine behavior.

Why Use duti

Problem: macOS "Open With" settings are:

  • Stored in binary plist files (hard to version control)
  • Reset unpredictably by macOS updates
  • Tedious to configure manually across machines
  • No scriptable interface

Solution: duti provides:

  • ✅ Command-line control over default applications
  • ✅ Scriptable configuration (version-controlled)
  • ✅ Idempotent application (safe to run multiple times)
  • ✅ Automated setup via chezmoi

Installation

duti is installed via Homebrew and configured automatically by chezmoi:

bash
brew install duti

Chezmoi integration: ~/.local/share/chezmoi/run_onchange_50-duti-defaults.sh runs automatically on chezmoi apply.

Current Configuration

Your duti configuration sets:

VS Code as default for coding file types:

  • JavaScript/TypeScript: .js, .ts, .jsx, .tsx, .mjs
  • System languages: .c, .cpp, .h, .java, .py, .rb, .go, .rs, .swift, .kt
  • Shell scripts: .sh, .zsh, .nu, .fish, .zshrc, .bashrc
  • Web/markup: .html, .css, .scss, .json, .yaml, .yml, .md, .xml
  • Config files: .toml, .ini, .cfg, .conf, .txt
  • LaTeX: .tex, .cls, .sty, .bib, .bst, .aux, .log
  • Build files: Makefile, Dockerfile, .cmake, .gradle

VLC as default for media files:

  • Video: .mp4, .avi, .mkv, .mov, .wmv, .flv, .webm, .m4v
  • Audio: .mp3, .wav, .flac, .aac, .ogg, .m4a, .wma, .opus
  • Subtitles: .srt, .vtt, .ass, .ssa, .sub, .idx

How It Works

Bundle Identifiers: duti uses macOS bundle IDs to identify applications:

ApplicationBundle ID
VS Codecom.microsoft.VSCode
VLCorg.videolan.vlc
Neovim (if needed)io.neovim.neovim
Safaricom.apple.Safari
Chromecom.google.Chrome

Find an app's bundle ID:

bash
osascript -e 'id of app "Visual Studio Code"'
# Returns: com.microsoft.VSCode

Basic Usage

Set default application for a file extension:

bash
duti -s com.microsoft.VSCode .py all

Set default for URL scheme:

bash
duti -s com.google.Chrome http all
duti -s com.google.Chrome https all

Check current default for extension:

bash
duti -x py
# Shows: VS Code (com.microsoft.VSCode)

List all configured defaults:

bash
duti -l

Editing Your Configuration

Your duti defaults are managed by chezmoi at:

~/.local/share/chezmoi/run_onchange_50-duti-defaults.sh

Edit workflow:

bash
# 1. Edit the script
chezmoi edit ~/.local/share/chezmoi/run_onchange_50-duti-defaults.sh

# 2. Add file extensions to arrays:
CODING_EXTENSIONS=(
  # ... existing extensions
  lua vim  # Add new extensions
)

MEDIA_EXTENSIONS=(
  # ... existing extensions
  gif webp  # Add image formats
)

# 3. Apply changes
chezmoi apply

# 4. Commit to Git
cd ~/.local/share/chezmoi
git add run_onchange_50-duti-defaults.sh
git commit -m "Add lua and vim to VS Code defaults"
git push

Common Customizations

Add Neovim as default for specific files:

bash
# In run_onchange_50-duti-defaults.sh
NVIM_BUNDLE_ID="io.neovim.neovim"

NVIM_EXTENSIONS=(
  zsh sh bash fish nu
  vimrc nvimrc
)

for ext in "${NVIM_EXTENSIONS[@]}"; do
  duti -s "$NVIM_BUNDLE_ID" ".$ext" all
done

Set browser for URL schemes:

bash
# Add to run_onchange_50-duti-defaults.sh
CHROME_BUNDLE_ID="com.google.Chrome"

duti -s "$CHROME_BUNDLE_ID" http all
duti -s "$CHROME_BUNDLE_ID" https all

Set different apps for specific extensions:

bash
# PDFs to Preview
duti -s com.apple.Preview .pdf all

# Images to Preview
duti -s com.apple.Preview .png all
duti -s com.apple.Preview .jpg all

Troubleshooting

Changes not applying:

bash
# Force re-run chezmoi script
chezmoi apply --force

# Or run script directly
bash ~/.local/share/chezmoi/run_onchange_50-duti-defaults.sh

Application not found:

  • Verify app is installed
  • Check bundle ID: osascript -e 'id of app "AppName"'
  • Some apps don't support file associations

Finder still shows wrong default:

  • Right-click file → Get Info → Open With: → Change All...
  • Or reboot (macOS caches Launch Services database)

Reset Launch Services database (nuclear option):

bash
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user
killall Finder

Integration with Chezmoi

Why run_onchange_ prefix?

  • Script runs automatically when its content changes
  • Idempotent (safe to run multiple times)
  • No manual intervention needed
  • Part of automated machine setup

When it runs:

  • On chezmoi apply after script changes
  • On new machine setup (chezmoi init)
  • Never runs if script hasn't changed (efficient)

See also: run_onchange_40-asdf-tools.sh, run_onchange_60-python-packages.sh

Advanced Usage

Export current defaults to file:

bash
duti > ~/my-defaults.duti

Import defaults from file:

bash
duti ~/my-defaults.duti

Set defaults per user vs system-wide:

bash
duti -s com.microsoft.VSCode .py all          # Current user
duti -s com.microsoft.VSCode .py system       # System-wide (requires sudo)
ToolPurpose
dutiSet default apps (what we use)
defaultsmacOS preferences system
open -aOpen file with specific app once
qlmanageQuick Look management

duti GitHub Repository

macOS Launch Services