macOS: Change the default text editor with duti

Tired of double-clicking a .txt file only to have it open in TextEdit when you'd rather use your preferred editor? On macOS, you can use duti (a command-line utility) to programmatically set default applications for specific file types. This is especially useful for developers who want their preferred editor to handle all text files by default.

What is duti?

duti is a command-line tool that allows you to set default applications for document types and URL schemes on macOS. It's particularly useful for developers who want to script their environment setup or make bulk changes to file associations.

Installation

The easiest way to install duti is via Homebrew:

brew install duti

Alternatively, you can download it from the official repository.

Finding Your Editor's Bundle Identifier

Before setting file associations, you need to find your editor's bundle identifier. Here are several methods:

Method 1: Using AppleScript

osascript -e 'id of app "Zed"'
# -> dev.zed.Zed

Method 2: Using mdls (metadata lookup)

mdls -name kMDItemCFBundleIdentifier -r /Applications/Zed.app
# -> dev.zed.Zed

Method 3: Common Editor Bundle IDs

Here are bundle identifiers for popular editors:

  • Zed: dev.zed.Zed
  • Visual Studio Code: com.microsoft.VSCode
  • Vim: org.vim.MacVim
  • Neovim: com.qvacua.VimR
  • TextEdit: com.apple.TextEdit
  • Sublime Text: com.sublimetext.4
  • Nova: com.panic.Nova

For other editors, replace the app name or path in the commands above accordingly.

Understanding Uniform Type Identifiers (UTIs)

On macOS, file associations are defined by Uniform Type Identifiers (UTIs) rather than just file extensions. UTIs provide a hierarchical system for identifying file types. Here are the most important UTIs for text files:

  • public.plain-text — plain text files (.txt, .text)
  • public.text — generic text content (broader category)
  • public.source-code — source code files
  • net.daringfireball.markdown — Markdown files (.md, .markdown)

Setting Your Default Editor

The basic syntax for duti is:

duti -s <bundle-identifier> <uti> <role>

Where:

  • is your editor's ID (e.g., dev.zed.Zed)
  • is the file type identifier
  • is typically all (handles opening, editing, etc.)

Basic Text Files

To set Zed as the default editor for basic text files:

# Set Zed for plain text files
duti -s dev.zed.Zed public.plain-text all

# Set Zed for generic text content
duti -s dev.zed.Zed public.text all

Source Code and Markdown

For development work, you'll likely want to handle source code and Markdown files:

# Markdown files
duti -s dev.zed.Zed net.daringfireball.markdown all

# Source code files (covers many programming languages)
duti -s dev.zed.Zed public.source-code all

Specific File Extensions

You can also set associations for specific file extensions:

# Set for .txt files specifically
duti -s dev.zed.Zed txt all

# Set for .md files
duti -s dev.zed.Zed md all

# Set for .js files
duti -s dev.zed.Zed js all

Quick Setup Script

Here's a comprehensive script to set Zed as your default editor for most text-based files:

#!/bin/bash

EDITOR_ID="dev.zed.Zed"

# Basic text types
duti -s $EDITOR_ID public.plain-text all
duti -s $EDITOR_ID public.text all
duti -s $EDITOR_ID public.source-code all

# Markdown
duti -s $EDITOR_ID net.daringfireball.markdown all

# Common file extensions
extensions=("txt" "md" "markdown" "js" "ts" "py" "go" "rs" "c" "cpp" "h" "hpp" "css" "html" "json" "yaml" "yml" "xml" "sh" "zsh" "bash")

for ext in "${extensions[@]}"; do
    duti -s $EDITOR_ID $ext all
done

echo "Default editor associations updated!"

Verify Your Associations

After making changes, you can verify which applications are set to handle specific file types:

Check by File Extension

# Which app opens .txt files?
duti -x txt

# Which app opens .md files?
duti -x md

# Which app opens .js files?
duti -x js

Check by UTI

# List all apps that can handle plain text
duti -l public.plain-text

# List all apps that can handle Markdown
duti -l net.daringfireball.markdown

Test Your Settings

Create a test file and double-click it to verify:

# Create a test file
echo "Hello, World!" > ~/Desktop/test.txt

# Double-click the file in Finder to test

Switching to Another Editor

To change to a different editor, simply substitute another bundle identifier. For example, to switch to Visual Studio Code:

# Switch to VS Code
duti -s com.microsoft.VSCode public.plain-text all
duti -s com.microsoft.VSCode public.text all
duti -s com.microsoft.VSCode net.daringfireball.markdown all

To revert back to the system default (TextEdit):

# Revert to TextEdit
duti -s com.apple.TextEdit public.plain-text all
duti -s com.apple.TextEdit public.text all

Advanced Usage

Using Configuration Files

You can create a duti configuration file for easier management:

# Create a duti settings file
cat > ~/.duti << EOF
dev.zed.Zed public.plain-text all
dev.zed.Zed public.text all
dev.zed.Zed net.daringfireball.markdown all
dev.zed.Zed public.source-code all
EOF

# Apply the settings
duti ~/.duti

Integration with Dotfiles

Add duti commands to your shell configuration or dotfiles setup script:

# In your ~/.zshrc or setup script
setup_editor_associations() {
    local editor_id="dev.zed.Zed"

    duti -s "$editor_id" public.plain-text all
    duti -s "$editor_id" public.text all
    duti -s "$editor_id" net.daringfireball.markdown all

    echo "Editor associations configured"
}

Troubleshooting

Changes Don't Take Effect

If your changes don't appear immediately:

  1. Quit and relaunch the target applications and Finder
  2. Log out and back in to refresh the system
  3. Clear Launch Services cache (advanced):
# Rebuild Launch Services database (use with caution)
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
  -kill -r -domain local -domain system -domain user

A logout/login may be necessary after rebuilding the cache.

Finding the Right UTI

If a file type isn't responding to the standard UTIs, investigate what UTI your system uses:

# Check what UTI a specific file has
mdls -name kMDItemContentType /path/to/your/file.ext

# Example: Check a Python file
mdls -name kMDItemContentType example.py

Common Issues

  • File still opens in wrong app: Some applications register their own UTIs. You may need to set associations for both the standard UTI and the app-specific one.
  • System overrides your settings: Some system updates or application installations may reset file associations.
  • Permissions issues: Ensure duti has the necessary permissions to modify system settings.

Useful duti Commands Reference

# Display help
duti -h

# List all UTIs for an extension
duti -l txt

# Show what app handles an extension
duti -x py

# Set default app for a UTI
duti -s com.app.bundle uti.identifier all

# Set default app for an extension
duti -s com.app.bundle ext all

# Apply settings from a file
duti /path/to/settings/file