Skip to content
macOSDeep Dive Published Updated 7 min readViews unavailable

plutil on macOS: Validate and Convert Property Lists Without Losing Types

Use macOS plutil to lint and convert XML or binary property lists safely, inspect value types, and recognize JSON's date and data limits.

Property lists look like text when Xcode displays XML, but they are typed object graphs, not just key/value text files. A Boolean, integer, real number, date, and data blob can all look deceptively similar after a conversion or a careless JSON round trip. Apple’s plutil command is useful for checking syntax, changing a plist representation, and extracting a value. It cannot decide whether the keys meet an application’s schema or whether a particular subsystem will accept the file.

The examples here target the built-in /usr/bin/plutil on macOS 27.0.1. The command’s help and less common options can vary by macOS release; inspect plutil -help on the system where a script will run. Keep a copy of important input and use a separate output path until the converted file has been checked.

A valid plist is not necessarily a valid configuration

Apple’s property-list model includes dictionaries, arrays, strings, data, dates, Booleans, integers, and floating-point values. XML and binary are two serializations of those property-list objects. Moving between those two forms should preserve the value types, but need not preserve whitespace, comments, indentation, key order, or the original byte representation.

Start with a syntax check:

plutil -lint ./Settings.plist

This answers whether the file can be parsed as a property list. It does not check required keys, allowed values, signing policy, deployment target, or application-specific constraints. A plist with a syntactically valid string where a subsystem expects a Boolean can pass -lint and still be rejected later. Use the consumer’s validator as a separate gate.

When a file parses but a setting is not taking effect, ask the consumer what it expects before editing the data. A launchd job, app information property list, profile payload, and code-signing entitlement can all contain plist data but have different schemas and acceptance rules. A successful parse is a useful first diagnostic, not proof that the configuration is operational.

Convert through a fresh output path

plutil -convert rewrites a file in the selected representation when no alternate output is supplied. That default is convenient interactively and risky in an automated edit: a typo in the format or an interrupted write can replace the only copy. Use -o to create a distinct output, then lint and inspect that artifact:

set -eu

src=./Settings.plist
work=$(mktemp -d) || exit 1
out="$work/Settings.binary.plist"
trap 'rm -f "$out"; rmdir "$work"' EXIT

plutil -lint "$src"
plutil -convert binary1 -o "$out" "$src"
plutil -lint "$out"
plutil -type Enabled "$out"
plutil -extract RetryCount raw -expect integer -o - "$out"

The final two commands should report bool and integer for keys that actually have those types. The raw extraction should print the integer value. The -expect check makes the type assumption executable: if RetryCount is a string or real instead, extraction fails rather than quietly treating it as the intended value. The key paths are examples; substitute keys from the plist being inspected.

The temporary directory created by mktemp -d is private to this run, and the trap removes only the named output and that directory. For a workflow that needs to retain the converted file, choose a new destination under the application’s staging area instead of using this cleanup pattern. Do not set out to the source path: the point is to preserve the input until verification is complete.

xml1 is useful when a human or a tool needs a readable XML representation; binary1 is useful when the consumer expects binary plist input. Use the format required by that consumer. Converting everything to XML is not a universal cleanup step, and converting to binary does not make invalid application settings valid.

Inspect the value, not a pretty-print approximation

The command offers two useful checks for a selected path:

plutil -type Enabled ./Settings.plist
plutil -extract RetryCount raw -expect integer -o - ./Settings.plist

-type reports the plist type at a key path. -extract emits the selected value in a chosen format; raw is appropriate for a scalar that a script needs to consume, while xml1 or binary1 emits a plist representation. An array path can include a numeric index. Keep the path constant or carefully validated if it is assembled by a script; it is not a shell expression and should not be confused with arbitrary key text.

plutil -p is convenient while exploring a file, but Apple’s command help explicitly marks its human-readable output as unsuitable for machine parsing and not stable. Do not build a parser around its spacing, dictionary order, or punctuation. For a script, select one field, request an explicit format, assert the expected type, and check the command’s exit status.

If a value surprises you, inspect the XML output and its type separately. The literal true in JSON, for example, is not a license to treat every numeric-looking field as a Boolean. Property-list Booleans and numeric values are distinct objects even though language bridges can represent both through number classes. Code that compares or serializes values should preserve that distinction deliberately.

JSON is a compatibility boundary, not another plist encoding

JSON has strings, numbers, Booleans, arrays, objects, and null, but no native property-list date or binary-data type. On this macOS 27.0.1 plutil, converting an otherwise valid plist whose value is a date or data blob to JSON fails with Invalid object in plist for JSON format; it does not round-trip those values as the original plist types. The tool help also identifies dates as unsupported for JSON output. Do not promise that arbitrary plist content can make a JSON round trip unchanged.

If a downstream API requires JSON, define an application-level mapping for dates and data. For example, specify whether a date becomes an RFC 3339 UTC string and whether data becomes base64, and keep that schema distinct from the property-list schema. A consumer may make different choices, and a string that happens to contain a timestamp or base64 text is still a string until the application interprets it.

Use XML or binary when the goal is to preserve the property-list type vocabulary. If the target tool accepts JSON only, test representative fixtures containing Boolean, integer, real, date, and data values through the exact producer and consumer versions. Validate types after import, not only whether the JSON parses.

A successful conversion does not satisfy the consumer

Some plist contexts have stricter representation rules. Apple’s notarization guidance explains that embedded entitlements must be well-formed XML for the relevant code-signing workflow; Xcode converts binary entitlement files to XML, and a binary embedded entitlement can be rejected by notarization. That does not mean all property lists must be XML. It means the consumer’s documented contract takes priority over a general format preference.

For a profile, bundle manifest, or launchd file, follow the specific schema and supported OS version. For signed code, verify the entitlements extracted from the finished signature and then run the appropriate signing and notarization checks. plutil -lint cannot validate a signature, repair a schema error, or prove that the installed system loaded the value.

Treat conversion failures as useful diagnostic boundaries:

  • A parse error means inspect the reported input and preserve the original before attempting repairs.
  • A type mismatch from -expect means the data does not match the script’s assumption; do not auto-coerce it.
  • A JSON conversion error for date or data means define an explicit mapping or keep a plist format.
  • A plist that passes lint but is ignored by its consumer needs schema, scope, version, and runtime-state checks.

For a release workflow, record the source checksum, tool version, source and destination formats, conversion exit status, and the type checks that matter. Compare semantic values rather than bytes when XML and binary encodings differ. If byte-for-byte provenance matters, retain the untouched source too; a valid conversion is not a substitute for an auditable original.

This workflow was exercised with /usr/bin/plutil on macOS 27.0.1: an XML fixture containing Boolean, integer, real, date, and data values converted to binary, linted successfully, and reported the same types for those fields. Separate date-only and data-only fixtures were rejected for JSON conversion. That verifies this installed implementation, not every historical macOS release or every consumer’s plist rules.

Related:

Sources:

Comments