The guide
A Property List Is a Small Typed Tree, and Each File Format Keeps a Different Part of It
Apple's settings files hold a tree of typed values. The binary, XML and JSON forms each carry that tree differently, so converting one is never only a matter of reformatting.
- 32 bytes the trailer that ends every binary property list
- 1 January 2001 the instant every stored date is counted from, in seconds
- 497 bytes the sample in binary form; the same tree as XML takes 1,296
- 2^53 - 1 the largest integer that JSON readers are sure to agree on
- 512 levels the nesting depth at which Apple's desktop reader stops
Read the full guide Fold the guide
Every Mac and every iPhone is full of small files whose names end in .plist. The name is short for property list, and such a file stores a serialized set of values, most often the settings of an app. That description is dry. What matters about the values is that every one of them has a type.
A tree whose leaves know what they are
A property list is a hierarchy built from a short, fixed set of types. The primitive ones are strings, numbers, binary data, dates and Boolean values, and a number is either an integer or a floating point value. The containers are arrays, which hold values by position, and dictionaries, which hold values by key. Containers can hold other containers. The value at the top is nearly always a dictionary or an array, though a single string, number or date can be a whole property list on its own. One rule is strict. Dictionary keys must be strings, and Apple’s open source code rejects a dictionary with any other kind of key.
These trees turn up wherever Apple software needs settings. An application bundle keeps a file named Info.plist in its Contents folder, holding facts about the app such as its vendor, its version, the names of its executable and icon files, and the permissions it requests. The files that store each user’s settings are property lists too. So are some files that go by other names: the .strings files that hold an app’s translated text are a reduced form of the oldest kind of property list.
Configuration profiles are one more case. A guide from the National Institute of Standards and Technology describes a profile as an XML file with a mobileconfig extension that carries a payload of settings, which a Mac can apply to itself. It calls profiles the preferred way to deliver settings, partly because they can be cryptographically signed. The institute’s own generator writes each profile with Python’s property list module.
Three forms, and how the binary one works
Apple’s systems read a property list in three forms: XML, a binary format, and an old-style text format that began in NeXTSTEP and was kept by OpenStep. The old style writes dictionaries in braces with a semicolon after each pair and arrays in parentheses. It knows only four types, which are strings, data, arrays and dictionaries, so a number or a Boolean has to travel as a string. Apple’s conversion tool still reads it and does not write it. The XML form gives every type its own element. It opens with the standard XML header, and a single plist element wraps exactly one value.
Why have a binary form as well? XML can be opened and edited in any text editor, but it is not a space-efficient way to store data. The binary format arrived in Mac OS X 10.2 for that reason and became the default for preference files in 10.4. Reading and writing JSON came later, in 10.7.
None of these files is the property list. Each is one way of writing the same tree down, and each can hold slightly different things.
The layout is stated in a comment in Apple’s open source reader. A file opens with the characters bplist and two more for the version, eight bytes in all. Then comes an object table, then an offset table, then a trailer. The trailer is 32 bytes: five unused, one for a sort version, one giving the size of an offset, one giving the size of an object reference, and three 8-byte numbers that say how many objects there are, which one is the top, and where the offset table begins. A reader therefore starts at the end of the file, not the beginning.
Every object starts with a marker byte. Its upper half names the type and its lower half gives a size or a count. An integer takes 1, 2, 4 or 8 bytes, or 16 for a value too large for 8. The three short sizes are read as unsigned, so a negative number always takes 8. A string is stored one byte to a character when it is ASCII and in 16-bit units when it is not. Containers are the important choice. An array or a dictionary does not contain its children. It holds references, small numbers that index the offset table, which gives the byte position of every object. The format is an index, not a compression scheme.
Dates show how plain the encoding is. A date is an 8-byte float, a count of seconds, and Core Foundation’s header gives the reference point as 00:00:00 on 1 January 2001. A date before that is a negative number.
The sample that opens with the converter on this page makes the arithmetic checkable. It holds 26 values. As binary it is 497 bytes; as XML, 1,296. The readout beside the download says 44 objects, 1-byte references, 2-byte offsets, and an offset table at byte 377. Add it up: 377 bytes of header and objects, 44 offsets of 2 bytes each, and the 32-byte trailer make 377 + 88 + 32 = 497. Keys are objects too, which is why there are 44. The tree has 18 distinct keys, and two of them, path and opened, serve two dictionaries but are stored once, because the writer keeps a single copy of equal strings, numbers, dates and data.
What JSON cannot say
JSON looks like the natural target for a conversion, and it is narrower than it looks. Its specification lists four primitive types, which are strings, numbers, booleans and null, and two structured types, objects and arrays. There is no date and no binary data. A converter therefore has to choose a spelling, and the usual ones are ISO 8601 text for a date and Base64 text for data. That works in one direction only. Coming back, nothing distinguishes a date from a string that happens to look like one.
Numbers are the quieter problem. The JSON specification observes that software built on 64-bit floating point is widespread, and that integers are interoperable, meaning every implementation agrees on their exact value, only within the range that ends at 2^53 - 1. A property list makes no such compromise. The sample has a key named largest whose value is 18446744073709551615, which the binary file stores as a marker and 16 bytes. A reader that turns JSON numbers into floats returns 18446744073709551616. It is off by one, silently.
The mismatch runs the other way too. JSON has null, and the XML form has no element for it. The marker table in Apple’s source also lists a set, and XML has no element for that either. An honest conversion says what it changed, or wraps every value in its type name so the way back is exact.
Archives full of UIDs
Open certain property lists and the tree looks nothing like settings. The top dictionary has four keys, $archiver, $objects, $top and $version, and scattered through it are values of a type missing from the list above: the UID. This is a keyed archive.
It exists because the type list is short. Apps want to save colors, fonts and whole objects of their own. A property list cannot describe an arbitrary object, so an extra layer of encoding is used. A class that supports coding hands its contents to an archiver, and the keyed archiver, introduced in Mac OS X 10.2, turns the object into a dictionary. The result is an ordinary property list, and it can itself be stored as binary data inside another one.
The shape follows from a problem every archiver faces. A set of objects in memory is not necessarily a tree. Two objects can refer to each other, and one object can be referenced by several, so the coder must store each object once and still be able to rebuild every reference. The open source archiver solves it with a flat list. Every object goes into $objects, and a reference is a UID, which is simply a position in that list. Position zero holds the string $null. Each archived object names its class through one more UID.
The binary format gives the UID a marker of its own. XML has no element for one, so Apple’s XML reader treats a dictionary whose only key is CF$UID as a UID. Python’s plistlib added UIDs in version 3.8, and its documentation says they are there for keyed archives.
Reading a file from a stranger
A binary property list is a set of claims. The offset table is here, this object starts there, this array has so many items. The file supplies every one of them, and a reader that believes them all can be led anywhere. Apple’s reader checks that the trailer describes a file of exactly the length it has, that every offset lies inside the object table, and that no reference exceeds the object count. It remembers the offsets it is inside, so a container that contains itself is caught. The newer open source code also stops at 512 levels on desktop systems and 128 on phones, a depth chosen so the stack cannot run out.
Sharing sets a subtler trap. Because containers hold references, sixty arrays that each list the next one twice fit in under 300 bytes and describe two to the sixtieth power values, with no cycle anywhere. The same header that sets the depth limit cites an audit finding about exponential growth. A reader that builds a tree has to count before it builds.
XML brings an older danger. Its specification lets a document type declaration define entities, including external ones named by a system identifier, and a parser that honors them can be made to fetch a file or to expand a few lines into an enormous document. The OWASP guidance is blunt: disable document type definitions wherever possible, reject DOCTYPE declarations if the parser allows it, and otherwise switch off external entities and limit expansion. A property list needs none of that machinery. Apple’s own reader is written by hand and steps over an inline definition without applying it.
One XML rule can even change data. The specification says carriage returns literally present in a document are removed or replaced by line feeds before anything else is done, so a strict parser alters a string that contains one. A careful writer spells that character as a numbered reference.
A viewer has limits, and they should be plain. The converter here does not read the old-style text form. It reads a property list and nothing wrapped around one, so a file that has been signed or encrypted as a whole must first be unwrapped by software that holds the means to do it. It shows a keyed archive as stored, a list of dictionaries and UIDs, and cannot rebuild the objects, which needs the classes that wrote them. And it shows what a file contains, not what the app that owns it will do with a value.
A signed configuration profile is the common example of a wrapped file. The NIST generator signs by passing the finished XML through a CMS signing command, which writes a separate signed file, and it is the unsigned one that opens here.
A property list is a short list of types, and the types are the point. Keep them through every conversion, or keep a list of the ones that were lost.
Sources
Every source consulted for this guide. Links checked October 6, 2026.
- CF/CFBinaryPList.c at main - apple-oss-distributions/CF - GitHub Apple, CoreFoundation open source
- CF/ForFoundationOnly.h at main - apple-oss-distributions/CF - GitHub Apple, CoreFoundation open source
- CF/CFDate.h at main - apple-oss-distributions/CF - GitHub Apple, CoreFoundation open source
- CF/CFPropertyList.c at main - apple-oss-distributions/CF - GitHub Apple, CoreFoundation open source
- swift-corelibs-foundation/Sources/CoreFoundation/internalInclude/CFPropertyList_Internal.h at main - swiftlang/swift-corelibs-foundation - GitHub Swift project, swift-corelibs-foundation
- swift-corelibs-foundation/Sources/Foundation/NSKeyedArchiver.swift at main - swiftlang/swift-corelibs-foundation - GitHub Swift project, swift-corelibs-foundation
- RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format RFC Editor, Internet Engineering Task Force
- XML External Entity Prevention - OWASP Cheat Sheet Series OWASP Foundation
- Extensible Markup Language (XML) 1.0 (Fifth Edition) World Wide Web Consortium (W3C)
- plistlib - Generate and parse Apple .plist files - Python 3.14.8 documentation Python Software Foundation
- Property list - Wikipedia Wikipedia
- Bundle (macOS) - Wikipedia Wikipedia
- Automated Secure Configuration Guidance from the macOS Security Compliance Project (mSCP) National Institute of Standards and Technology, Special Publication 800-219 Revision 1
- macos_security/scripts/generate_guidance.py at sequoia - usnistgov/macos_security - GitHub National Institute of Standards and Technology, macOS Security Compliance Project