Want to change Finder’s preferred view to column view from the command line? There’s a
defaults command for that. Want to alter trackpad behavior from a shell script? Yep,
defaults can do it.
Lots of (most?) macOS and iOS settings manifest in the defaults subsystem (see Apple’s Preferences Programming Topics for Core Foundation and UserDefaults). What this means for you, the macOS user, is many of these settings exist somewhere on disk in the form of
.plist files. You can find them in
~/Library/Preferences, among other places.
There is a canonical utility provided by the operating system to access and manipulate many of these preferences:
The man page for
defaults does a fair job of describing the command’s simpler uses, but you quickly hit more complex use cases that aren’t addressed. While there’s a fair bit of discussion on blogs and stackoverflow about the command’s use, most of that is incomplete as well. Many complain about
defaults’s limitations, and recommend use of
/usr/libexec/PlistBuddy. It’s true that
defaults is limited in many ways, and
PlistBuddy allows much more complex manipulations of plist files. But
defaults works at a higher level of abstraction, using the defaults subsystem, without necessarily making assumptions about which specific file or files on disk represent the configuration. As such, if it’s possible to use
defaults that’s preferable. We’ll dig into some of the more advanced uses, but before that here’s a quick plist refresher.
What is a plist?
It’s worth checking out the
plist(5) man page, but the essence is that a plist is a serialized object graph. Unlike more generic object serialialization, plist composition is restricted to a handful of data types:
- Top level object must be a dictionary or array
- The top object may only be composed of:
- Numbers (floats and integers)
- Data (an opaque blob of bytes)
As described above the only container types allowed at any level of the object graph are dictionary and array, which may themselves be composed of any of the above types.
Although property lists primarily exist in either binary or XML formats (there are also JSON and the Perl-ish NeXTSTEP formats), it’s easier to visualize the XML, so we’ll stick with that format. Here’s an example plist dictionary:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>top-level-key</key> <dict> <key>subkey1</key> <integer>1</integer> </dict> </dict> </plist>
Here we have a dictionary with a single key/value pair. The key being
top-level-key, and the value being a nested dictionary. Strictly speaking a plist can be a dictionary or array, as described above, but as far as I know, defaults are always dictionaries. At any rate,
defaults(1) can only modify dictionaries.
Technically, plist is just a data format. You may find files on your Mac that are plist data but are not user defaults/preferences. You can view them, but they’ll be nonsensical. In macOS and iOS, IOKit, CoreFoundation, and Foundation all have serialization APIs that write arbitrary object graphs out to XML or binary plist streams. Many applications save state in this form and then unserialize that state later.
Reading a plist
You can read preferences from a domain or a specific plist using the defaults command. I’ll describe that below, but its usefulness is limited. Defaults gives you output in the old perl-like NextSTEP format. This is human-readable but doesn’t convey the literal contents of the plist. Moreover, it doesn’t offer useful insight into what
defaults will accept as input when writing values. Instead, use
plutil(1). To view the XML version of a plist (whether it’s in binary format or already in XML), use
plutil to convert it and write the output to standard out:
$ plutil -convert xml1 -o - ~/Library/Preferences/com.apple.finder.plist
In fact, this is a good alias to add to your shell:
$ alias plcat='plutil -convert xml1 -o -' $ plcat ./foo.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>beast-mode</key> <true/> <key>top-level-key</key> <dict> <key>subkey1</key> <real>1</real> </dict> </dict> </plist>
User defaults are organized into domains, which are sort of an abstract and can take a few different forms. They’re often a reverse-DNS-style namespace that an application uses to store its settings. For example, Finder uses
com.apple.finder. TextMate uses
com.macromates.textmate. The reverse-DNS form isn’t strictly necessary, but it helps apps avoid namespace collisions with other apps.
There are a couple of other domain forms worth noting. First is the global domain, or
NSGlobalDomain. Defaults accessed by an app that aren’t in that app’s own domain are looked up from the global domain.
There’s also the domain form of a literal file path. There are cases where, as far as I know, the only way to change a setting is to use this form (for example, many root-owned plists in
A preference domain consists of a dictionary of keys and values, where keys are strings and the values are any of the plist types described above. You can view a list of domains available to your user:
$ defaults domains
You’ll get a long, comma separated list of domains containing user defaults.
You can use the defaults command to perform a variety of operations on a preference domain. The man page refers to these operations as “commands.” The operations you’ll use most include: - Reading default key - Adding a key - Changing an existing key’s value - Deleting a key
There are others as well (such as renaming a key) which are worth reading about in the man page.
So how do you manipulate settings with defaults? The basic form to read a default is:
$ defaults read <domain> <key>
To delete a key:
$ defaults delete <domain> <key>
To set or change a key, use the
write command. We’ll focus primarily on this since writing defaults gets complicated quickly. This takes the form:
$ defaults write <domain> <key> <value>
The key from above is always a key in the top-level dictionary. There are ways to set values for keys in sub-dictionaries, which we’ll get into. It’s important to remember, though, that in all cases, you’re setting or modifying the value for a key in the outermost dictionary. When writing a key, if it doesn’t exist it will be created. In other words, modifying a key’s value is indistinguishable from adding a new key/value pair to the dictionary.
The value bit is where it gets tricky, and where a lot of the hidden flexibility of
defaults comes in. We’ll explore this in depth.
Setting a default
Let’s say you want to write settings to domain
foo.bar. Doing so would create (or modify) the file
~/Library/Preferences/foo.bar.plist. Alternatively, you could create this plist on your Desktop with
~/Desktop/foo.bar.plist. That gives us:
$ defaults write ~/Desktop/foo.bar.plist <key> <value>
If you wanted to set a key/value pair of beast-mode:True in
foo.bar, you need to specify
beast-mode as the key:
$ defaults write ~/Desktop/foo.bar.plist beast-mode <value>
Of course, you require a value. It’s not as simple as using “true” though.
$ defaults write ~/Desktop/foo.bar.plist beast-mode true $ plcat ~/Desktop/foo.bar.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>beast-mode</key> <string>true</string> </dict> </plist>
Above, the ‘true’ got interpreted as a string. Although in many cases, that might actually work, what you really intended was a boolean. So, ‘value’ gets a bit more complicated. Rather than
true, in this case value must specify a type:
$ defaults write ~/Desktop/foo.bar.plist beast-mode -bool true $ plcat ~/Desktop/foo.bar.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>beast-mode</key> <true/> </dict> </plist>
This time ‘true’ got turned into a proper boolean. If you consult the man page, there are lots of type arguments, and some of them get pretty confusing:
-string(the default if nothing is specified)
-float(which translates, confusingly, into the
<real>plist xml element)
-bool(case insensitive yes, no, true, or false)
From the above list, string, int, float, and bool are pretty obvious. But for the rest, the man page gives no examples of their actual use. So you’re left scouring stackoverflow for examples, and then trying to work backwards to generalize rules for their use.
Here are some things that the most discussion misses. These allow us to describe a generalized use of
- You’re always modifying the value associated with a key in the top level dictionary
- Always specify the type even in cases where the type is implied
- You can provide the value in many forms, but the form that works in all situations (and is undocumented) is the XML plist fragment. When in doubt, use this.
The plist fragment form turns out to be pretty useful. It works like this; you could set a string value for the dictionary key,
mystring, like so:
defaults write foo.bar mystring '<string>some-string-value</string>'
Note that when using the plist fragment form, the type is encoded in XML, so no type argument is required.
This is more verbose than simply:
defaults write foo.bar mystring -string some-string-value
Or even shorter:
defaults write foo.bar mystring some-string-value
Maybe you want to use the shorter forms when entering the command manually. But by generalizing the form, we can know a syntax that is guaranteed to work, and not wonder why a command should work but doesn’t.
If we’re interested in scripting
defaults commands, or even using them in configuration management such as Ansible, it doesn’t really matter how verbose the command is.
Let’s go through each of the type arguments and explore how it’s used in our generalized form. I’ll also show the shorter form where appropriate
Discussion: Plist values default to strings if you don’t specify.
XML Plist Element:
defaults write foo.bar mystring '<string>some-string-value</string>' defaults write foo.bar mystring -string some-string-value defaults write foo.bar mystring some-string-value
XML Plist Element:
Caveats: A float like
1.0 won’t be downcast to an integer when using the XML form. The command will fail. Be sure to use only actual integral values.
defaults write foo.bar myint '<integer>1</integer>' defaults write foo.bar myint -int 451
XML Plist Element:
defaults write foo.bar myreal '<real>3.14</real>' defaults write foo.bar myreal -float 3.14
XML Plist Element:
defaults write foo.bar mystring -bool True defaults write foo.bar mystring -bool YES defaults write foo.bar mystring '<true/>'
The “data” type is a sort of get-out-of-jail-free card. It’s all data, right? So “data” is opaque data that isn’t one of the other plist data types. All that matters is that’s it’s base64 encoded. Here are some examples of what it could be:
- A jpeg file (look at
<key>jpegphoto</key>in your own user record in
- Another plist (Look at
<key>ShadowHashData</key>in your user record. It’s a base64-encoded binary plist that represents your hashed password.)
- A serialized object that isn’t compatible with plist data types
XML Plist Element:
Caveat: Must be base64 encoded
defaults write foo.bar mydata "<data>$(echo -ne 'Quick to the point to the point no faking'|base64 -o -)</data>"
This one’s a little confusing because the date must be formatted in a particular way. Namely, ISO-8601. And because you have more important things to worry about, that standard is:
Data elements and interchange formats – Information interchange – Representation of dates and times
Here’s the wikipedia article:
Go read that and just before you decide that none of this is worth it because under 8601 there are still like a million date representations, come back.
I can’t find any formal documentation on how
defaults wants its dates formatted. The man page says:
-date Allows the user to specify a date as the value for the given preference key.
In my testing, it’s only happy with fairly narrow subset of ISO 8601. Specifically, it seems to want year + month + day + hour + minutes + seconds in the UTC time zone:
Interestingly, you can give it a time with slightly nonsensical times, such as 62 minutes past the hour, and it will parse that time and write out a properly normalized time. For example:
XML Plist Element:
Caveats: input date must be a narrow subset of ISO 8601 (see discussion)
defaults write foo.bar mydate -date "2018-06-15T17:16:26Z" defaults write foo.bar mydate "<date>2018-06-15T17:16:26Z</date>"
We’ve covered the primitive plist types and how to add them as values using
defaults. Before moving on to the container types, let’s pause to discuss the ways they can get complicated.
For container types, there aren’t arguments I’m aware of to specify the data types they contain. For example, the man page gives as an example for adding an array:
defaults write somedomain preferenceKey -array element1 element2 element3
If you do that, you get:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>preferenceKey</key> <array> <string>element1</string> <string>element2</string> <string>element3</string> </array> </dict> </plist>
But what if the array is composed of integers? Or floats? Or a combination of types? Or what if you want one of the array members to be another array? Or a dictionary? I’m not aware of an equivalent of
-dict that can be applied to individual array members.
This is where the plist fragment trick gets really useful. It gives us a way to specify data types. So, instead of the above command which treats the array members as strings, we can do:
defaults write foo.bar myarray \ '<array><integer>1</integer><real>3.14</real></array>'
So here are the container data types and how to add them as values.
Discussion: Adds an array of objects as the value for the given key. The objects may be arbitrarily complex.
XML Plist Element:
Caveats: There is no command line argument to specify the data types that make up the array. To do that, encode the array as XML.
defaults write foo.bar myarray -array 1 2 # Adds 1 and 2 as strings defaults write foo.bar myarray \ <array><integer>1</integer><integer>2</integer></array>
Discussion: Allows adding of arbitrary objects of any plist type to the end of an array, creating the array if it doesn’t already exist. Added objects should be space separated even when in XML form (see examples). Added objects may be arbitrarily complex.
Caveats: Members of an existing array cannot be deleted or rearranged. New items cannot be added anywhere but the end of an array. Arrays that require something more complex than items appended to the end should be added from scratch with
defaults write foo.bar myarray -array-add 1 2 3 defaults write foo.bar myarray -array-add \ '<integer>1</integer>' \ '<integer>2</integer>' \ '<integer>3</integer>' defaults write foo.bar myarray -array-add \ '<dict><key>subkey</key><integer>7</integer></dict>'
Discussion: Adds a dictionary as the value for the given key. The dictionary may contain arbitrarily complex objects.
XML Plist Element:
Caveats: There is no command line argument to specify the data types that make up the dictionary. To do that, encode the array as XML.
#add a simple dictionary of key/string pairs defaults write foo.bar mydict -dict key1 value1 key2 value2 #more complex variants using XML representation of the dictionary defaults write foo.bar mydict \ '<dict> <key>key1</key> <real>3.14</real> </dict>' #nested dictionary defaults write foo.bar mydict \ '<dict> <key>subdict</key> <dict> <key>subkey1</key> <date>2018-06-15T17:16:26Z</date> </dict> </dict>'
Discussion: Adds the provided key/value pair to a dictionary associated with the given key. The subdictionary is created if it doesn’t exist. If the subkey already exists in the subdictionary, its value is replaced. The value added to the subdictionary may be arbitrarily complex.
- This only modifies a subdictionary at the second level of the object graph.
- Subkey/value pairs cannot be deleted.
- In order to perform these more complex operations, the entire subdictionary should be replaced using
defaults write foo.bar mydict -dict-add key1 value1 defaults write foo.bar mydict -dict-add key2 '<real>3.14</real>' defaults write foo.bar mydict -dict-add key3 \ '<dict> <key>subkey1</key> <date>2018-06-15T17:16:26Z</date> </dict>'
That should be a fairly complete explanation of the various ways you can manipulate macOS/iOS defaults and their backing plists, with an emphasis on how to write keys and values to defaults dictionaries. Here’s a summary of key points:
- Defaults are dictionaries of user preferences that are backed by plist files on disk
- User defaults can be read and modified by
- Reading and deleting can be performed per defaults domain, or per dictionary key
- Writing a default always sets or modifies a specific key/value pair
- Setting or changing a key/value pair is always an operation on a key in the outermost dictionary
- This is true even when modifying a nested dictionary;
- In effect, by adding to a nested container, you’re changing the value associated with a top level dictionary key
- With primitive (int, float, etc.) plist types you can use either the type argument or encode the key’s value as an XML plist fragment
- Using XML plist fragments you can add really complex structures to defaults dictionaries or subdictionaries, such as mutliple nested dictionaries and arrays
- To do more complex operations on subdictionaries or arrays such as deleting elements, you need to re-add the entire structure as a value associated with the specified key using
In the next post I’ll share a tool I’ve built that will help automate the process of identifying where a user default is actually stored and then autogenerate a
defaults incantation to write that setting.