How macOS Preference Domains Work
macOS stores preferences in a layered system: per-user preferences live in `~/Library/Preferences/`, system-wide preferences live in `/Library/Preferences/`, and a managed layer sits on top of both for MDM-enrolled machines. Each preference file corresponds to a domain, which is typically a reverse-DNS bundle identifier like `com.apple.dock` or `com.apple.finder`.
The `defaults` binary is a front-end to the CFPreferences API. When you write a value with `defaults write`, the change goes through `cfprefsd`, which caches it in memory and writes to disk asynchronously. This matters for scripting: a write followed immediately by a read may return a stale value if `cfprefsd` hasn't flushed. In our experience, a `killall cfprefsd` after batch writes forces a flush, though this logs warnings in Console.app.
There are three scope flags: `-app` (takes an app name or bundle ID), `-g` or `-globalDomain` (NSGlobalDomain, which all apps inherit), and no flag at all (defaults to the current user's domain for whatever domain string you pass). The `-currentHost` flag targets `~/Library/Preferences/ByHost/`, which stores hardware-specific settings that should not roam with a user profile.
# List all domains visible to the current user
defaults domains
# Same output, one domain per line
defaults domains | tr ',' '\n' | sort | grep apple
# Read all keys in a domain
defaults read com.apple.finder
# Read a single key
defaults read com.apple.finder ShowPathbar
# Read from the global domain
defaults read -g AppleFontSmoothing
Data Types and How to Specify Them
Every value in the defaults system has an explicit type. Getting the type wrong produces either a silent failure or a corrupted preference that causes the target app to crash on launch. The valid type flags are: `-string`, `-integer`, `-float`, `-boolean` (or `-bool`), `-date`, `-data`, `-array`, `-array-add`, `-dict`, and `-dict-add`.
Boolean values accept `true`, `false`, `YES`, `NO`, `1`, and `0` - all are equivalent. Integers are stored as CFNumber with integer type; floats as CFNumber with float type. If you pass `1.0` with `-integer`, macOS stores it as integer 1, which is usually fine, but some apps check the CFNumber type and behave differently.
Arrays and dicts require special syntax. For arrays, list values as space-separated arguments after the type flag. For dicts, alternate key-value pairs. In our experience, the safest approach for complex structures is to write a plist fragment and use `defaults import`.
Dates use ISO 8601 format. The `-data` type takes a hex string with no spaces. You will rarely use `-data` directly; it is more common when reading raw binary plist values that `defaults` cannot display as text.
# Boolean
defaults write com.apple.finder ShowPathbar -bool true
# Integer
defaults write com.apple.dock tilesize -integer 48
# Float
defaults write -g com.apple.trackpad.scaling -float 2.5
# Array
defaults write com.apple.dock persistent-apps -array
# Array-add (append without replacing)
defaults write com.apple.dock recent-apps -array-add 'tile-data file-label Terminal '
# Dict
defaults write com.apple.universalaccess closeViewScrollWheelToggle -dict enabled -bool true
# Import from plist file
defaults import com.example.myapp ~/myapp-prefs.plist
High-Value Hidden Keys: Finder, Dock, and Screenshot
These are the keys we actually use on managed macOS fleets. Each one controls behavior that either has no GUI or whose GUI is buried under four dialog boxes.
`AppleShowAllFiles` in the Finder domain shows hidden files without toggling Cmd+Shift+. This is essential on developer machines where dotfiles need to be visible in Open dialogs. After writing this key, restart Finder with `killall Finder`.
`NSWindowShouldDragOnlyBackground` in NSGlobalDomain makes windows move only when you drag the title bar, not anywhere on the empty window background. On high-resolution displays with many overlapping windows, this prevents accidental moves.
`com.apple.screencapture` controls where screenshots land, their format, and whether the shadow is included. The `location` key accepts any valid path. The `type` key accepts `png`, `jpg`, `pdf`, `gif`, and `tiff`. Setting `disable-shadow` to true removes the window drop shadow from Cmd+Shift+4+Space captures, which matters when you need clean screenshots for documentation.
The `com.apple.dock` domain has over 40 writable keys. `autohide-delay` is a float in seconds; set it to `0` to eliminate the hover delay. `autohide-time-modifier` controls animation speed; `0` makes it instant. `launchanim` toggles the bouncing launch animation. After writing Dock keys, run `killall Dock` to apply.
For `com.apple.Safari`, the key `IncludeDevelopMenu` enables the Develop menu programmatically, which you need for WebDriver support in UI test pipelines.
# Finder: show all hidden files
defaults write com.apple.finder AppleShowAllFiles -bool true
killall Finder
# Finder: show full POSIX path in title bar
defaults write com.apple.finder _FXShowPosixPathInTitle -bool true
# Finder: disable .DS_Store on network volumes
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
# Screenshot: save to ~/Pictures/Screenshots, use jpg, no shadow
defaults write com.apple.screencapture location ~/Pictures/Screenshots
defaults write com.apple.screencapture type jpg
defaults write com.apple.screencapture disable-shadow -bool true
# Dock: instant autohide, no animation
defaults write com.apple.dock autohide-delay -float 0
defaults write com.apple.dock autohide-time-modifier -float 0
defaults write com.apple.dock launchanim -bool false
killall Dock
# Safari: enable Develop menu
defaults write com.apple.Safari IncludeDevelopMenu -bool true
System-Level Keys That Require sudo
Keys in `/Library/Preferences/` are system-wide and require root. You pass `-currentHost` or just target the system domain path directly. The behavior is the same as user defaults but you must invoke `defaults` with `sudo`.
`com.apple.loginwindow` controls what appears on the login screen. The `LoginwindowText` key sets a custom message visible to anyone at the login screen, useful for compliance banners. `SHOWFULLNAME` forces the full name + password fields instead of the user list.
`com.apple.SoftwareUpdate` controls AutomaticCheckEnabled, AutomaticDownload, and CriticalUpdateInstall. On unmanaged machines you can lock these with sudo. On MDM-enrolled machines, the configuration profile wins and your `defaults write` will be silently overridden on next MDM check-in.
`com.apple.TimeMachine` has `MaxSize` (in megabytes) to cap local snapshot storage and `AutoBackup` to toggle it. Writing these as root affects all users.
One key we use on every new macOS server: `com.apple.smb.server` with `NetBIOSName` and `ServerDescription` to set the SMB hostname independently of the computer name, which avoids the 15-character NetBIOS truncation that produces confusing names on Windows-heavy networks.
# Login window: compliance banner
sudo defaults write /Library/Preferences/com.apple.loginwindow LoginwindowText \
"Authorized use only. All activity is monitored."
# Login window: disable user list, require manual username entry
sudo defaults write /Library/Preferences/com.apple.loginwindow SHOWFULLNAME -bool true
# Disable automatic software update downloads
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -bool false
# Set SMB server name explicitly
sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server \
NetBIOSName -string "MACSERVER01"
# Cap Time Machine local snapshots at 50 GB
sudo defaults write /Library/Preferences/com.apple.TimeMachine MaxSize -integer 51200
Reading Unknown Keys: Discovery Techniques
The defaults man page documents the command syntax but not the keys. Finding undocumented keys requires observation. The canonical method is `opensnoop` or `fs_usage` to watch which plist files an app reads when you change a setting.
With `fs_usage`, filter on the Preferences directory: run `sudo fs_usage -f filesys | grep Preferences` in one terminal, then change a GUI setting, and watch which domain gets written. The key name will not appear in `fs_usage` output, but you now know the domain. Then diff `defaults read
A faster approach: use `defaults read com.example.app > /tmp/before.txt`, toggle the setting, then `defaults read com.example.app > /tmp/after.txt`, then `diff /tmp/before.txt /tmp/after.txt`. We have a one-liner version of this in our provisioning repo.
`plutil -p ~/Library/Preferences/com.apple.finder.plist` is an alternative to `defaults read` that sometimes shows keys `defaults` misses, particularly for newer plist formats. `PlistBuddy` at `/usr/libexec/PlistBuddy` can read and write nested structures more reliably than `defaults` for complex dict hierarchies.
For third-party apps, some preferences live in `~/Library/Application Support/
# Watch filesystem activity for preference writes
sudo fs_usage -f filesys 2>/dev/null | grep -i preferences
# Diff a domain before and after a GUI change
defaults read com.apple.universalaccess > /tmp/ua_before.txt
# ... change a setting in System Settings ...
defaults read com.apple.universalaccess > /tmp/ua_after.txt
diff /tmp/ua_before.txt /tmp/ua_after.txt
# Read a plist with plutil (handles newer binary plist formats)
plutil -p ~/Library/Preferences/com.apple.finder.plist | head -40
# PlistBuddy: read a nested key
/usr/libexec/PlistBuddy -c "Print :DesktopViewOptions:IconSize" \
~/Library/Preferences/com.apple.finder.plist
# PlistBuddy: write a nested key
/usr/libexec/PlistBuddy -c "Set :DesktopViewOptions:IconSize 64" \
~/Library/Preferences/com.apple.finder.plist
Scripting defaults for Fleet Provisioning
A provisioning script that applies defaults settings is the first thing we run on a new macOS machine, before any package manager or app installation. The structure is straightforward: apply all `defaults write` commands, then restart affected processes with `killall`.
The key discipline is idempotency. Every `defaults write` is naturally idempotent - running it twice produces the same result as running it once. This makes the script safe to re-run after an OS upgrade that resets some preferences.
For team provisioning, keep your defaults script in version control alongside your dotfiles. We store ours in a shell script named `macos-defaults.sh` that the rest of the provisioning system calls. If you are already using a tool like Ansible to manage macOS nodes, the `community.general.osx_defaults` module wraps the `defaults` binary with proper idempotency checks and change reporting.
When provisioning machines at scale - particularly in DevOps pipelines where macOS build agents need consistent configuration - tools that combine defaults management with broader automation are worth evaluating. Platforms like taskbotshub.ai handle orchestrating shell-level provisioning steps across fleets of macOS agents, which is useful when you need to push a new defaults key to 40 build machines without SSH-ing into each one manually.
One gotcha: some defaults keys only take effect for apps that are not currently running. If Safari is open when you write `IncludeDevelopMenu`, the change may not apply until relaunch. Structure your provisioning script to apply all defaults before launching any target applications, or explicitly kill and relaunch them.
#!/usr/bin/env bash
# macos-defaults.sh - idempotent macOS preference provisioning
# Tested on macOS Sequoia 15.3
set -euo pipefail
echo "Applying Finder preferences..."
defaults write com.apple.finder ShowPathbar -bool true
defaults write com.apple.finder ShowStatusBar -bool true
defaults write com.apple.finder AppleShowAllFiles -bool true
defaults write com.apple.finder _FXShowPosixPathInTitle -bool true
defaults write com.apple.finder FXDefaultSearchScope -string "SCcf"
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
echo "Applying Dock preferences..."
defaults write com.apple.dock tilesize -integer 48
defaults write com.apple.dock autohide -bool true
defaults write com.apple.dock autohide-delay -float 0
defaults write com.apple.dock autohide-time-modifier -float 0
defaults write com.apple.dock launchanim -bool false
defaults write com.apple.dock show-recents -bool false
echo "Applying global preferences..."
defaults write -g ApplePressAndHoldEnabled -bool false
defaults write -g InitialKeyRepeat -integer 15
defaults write -g KeyRepeat -integer 2
defaults write -g NSAutomaticSpellingCorrectionEnabled -bool false
defaults write -g NSDocumentSaveNewDocumentsToCloud -bool false
echo "Applying screenshot preferences..."
mkdir -p ~/Pictures/Screenshots
defaults write com.apple.screencapture location ~/Pictures/Screenshots
defaults write com.apple.screencapture type png
defaults write com.apple.screencapture disable-shadow -bool true
echo "Restarting affected processes..."
killall Finder
killall Dock
echo "Done. Some changes require logout/login to take full effect."
Exporting, Backing Up, and Diffing Preference State
Before applying any bulk defaults changes on a production machine, export the current state. `defaults export` writes a full domain snapshot to a plist file you can restore later.
For a full user preference backup, loop over all domains. The output is one plist per domain, which you can commit to git. On our test server, a full export of all user preference domains produces roughly 180 files totaling about 2.4 MB for a typical developer account.
Diffing preference state between two machines is useful for debugging behavior differences. Export the same domain from both machines, convert both plists to XML with `plutil -convert xml1`, then run a standard `diff`. Binary plist files are not directly diffable, so the `plutil` conversion step is necessary.
The `defaults export` output is a standard Apple XML plist. You can parse it with `xmllint`, `python3 -c 'import plistlib'`, or any plist-aware tool. We use Python's `plistlib` module in CI to assert that specific keys have expected values after a provisioning run, which gives us a quick sanity check before promoting a build agent image.
If you are naming internal tools or automation scripts built around defaults management - something you might publish or share with a team - choosing a clear, memorable name matters. Services like nicename.me can help find short, available domain names for tooling projects if you are taking your defaults automation public.
# Export a single domain
defaults export com.apple.finder ~/finder-backup.plist
# Restore from export
defaults import com.apple.finder ~/finder-backup.plist
# Export all user domains to a directory
mkdir -p ~/prefs-backup
for domain in $(defaults domains | tr ',' '\n' | tr -d ' '); do
defaults export "$domain" "$HOME/prefs-backup/${domain}.plist" 2>/dev/null || true
done
echo "Exported $(ls ~/prefs-backup | wc -l) domains"
# Convert binary plist to XML for diffing
plutil -convert xml1 ~/prefs-backup/com.apple.finder.plist -o /tmp/finder-machine1.xml
# On the second machine, do the same, then:
diff /tmp/finder-machine1.xml /tmp/finder-machine2.xml
# Python: assert a key has the expected value in CI
python3 -c "
import plistlib, sys
with open('$HOME/prefs-backup/com.apple.dock.plist', 'rb') as f:
p = plistlib.load(f)
assert p.get('tilesize') == 48, f'Expected 48, got {p.get(\"tilesize\")}'
print('Dock tilesize: OK')
"
Limitations and Known Failure Modes
Several defaults behaviors have changed across macOS versions and will cause silent failures if you are not aware of them.
From macOS Ventura (13) onward, writing to `com.apple.Terminal` does not affect the sandboxed Terminal app in all cases because Terminal reads some preferences through an XPC service. The write succeeds but the running app ignores it until relaunch. The same applies to Mail and several other Apple apps that Apple progressively sandboxed between macOS 12 and 15.
The `defaults` command does not work with preferences stored in SQLite databases. Starting in macOS Monterey, some system components moved settings to SQLite-backed stores that have no `defaults` interface. `com.apple.bluetooth.analytics` is one example. You will get no error; the domain simply shows no keys or returns an empty dict.
The `-currentHost` scope is machine-specific. It uses the hardware UUID as part of the plist filename, which means preferences written with `-currentHost` do not transfer when you migrate to a new machine with Migration Assistant. For provisioning scripts, avoid `-currentHost` unless you specifically need hardware-bound settings.
MDM profiles create a managed layer that cannot be overridden by `defaults write` even as root. If a ConfigurationProfile sets `com.apple.screencapture location`, your `defaults write` will appear to succeed but the managed value wins. You can detect managed keys with `defaults read` - they appear in the output but cannot be changed. `profiles show -type configuration` lists installed profiles and their payloads, which tells you which domains are locked.
Finally, `defaults write` to a domain that belongs to an app currently in a sandbox may fail silently or succeed but write to the wrong container. Apps distributed through the Mac App Store use sandboxed containers at `~/Library/Containers/
# Check if a domain has managed (MDM-locked) keys
# Managed keys show up in defaults read but cannot be written
defaults read com.apple.screencapture 2>/dev/null
# List installed configuration profiles
profiles show -type configuration 2>/dev/null | grep -A2 'PayloadType'
# Access a sandboxed Mac App Store app's preferences
# Example: Reeder 5 (bundle ID com.reederapp.5.macOS)
/usr/libexec/PlistBuddy -c "Print" \
~/Library/Containers/com.reederapp.5.macOS/Data/Library/Preferences/com.reederapp.5.macOS.plist
# Detect hardware UUID for -currentHost scope (for documentation)
system_profiler SPHardwareDataType | grep 'Hardware UUID'
# Verify what scope a key is actually stored in
defaults read-type com.apple.finder ShowPathbar