rea-reverse

Binary plist viewer and NSKeyedArchiver decoder

Drop a binary plist (bplist00) or an XML plist to read it as XML, the way plutil -convert xml1 writes it, and as JSON. If the file is an NSKeyedArchiver archive, its object graph is decoded too: arrays, dictionaries, strings, dates, UUIDs and URLs instead of $objects and CF$UID numbers.

Your plists never leave your browser. The file is read locally in a Web Worker that has no network access. How to verify this yourself

Open the tool on its own page (no ads, no analytics)

Most .plist files on a Mac or inside an app bundle are binary: they start with bplist00 and look like noise in a text editor. On a Mac, plutil -convert xml1 turns them back into XML. On Windows or Linux, or with a plist pulled out of an iOS backup, a crash log or a database blob, you usually need a script. Archives written by NSKeyedArchiver are worse: even as XML they are a flat $objects array full of CF$UID numbers.

This viewer does both in your browser. Drop the file and read it as XML, as JSON and, for keyed archives, as the object graph the app actually stored.

How to use

  1. Get the plist file: copy it out of the app bundle (Contents/Info.plist, ~/Library/Preferences/*.plist), an iOS backup or an IPA, or save the bytes of a database column that starts with bplist00.
  2. Drop it onto the drop zone above, or press Choose a file. Binary and XML plists with any extension are accepted.
  3. Read the Summary first: binary or XML, object count, and whether it is an NSKeyedArchiver archive. Then open the object graph (keyed archives), the XML plist or the JSON, and download the view you need.

Real example output

We dropped the keyed archive into this page and copied what it displayed. Layout condensed (label and value on one line, buttons left out); every value is verbatim. The Decoded in time varies per run.

Sample: test/plist/keyed.bplist, an NSKeyedArchiver archive that Foundation wrote from the Swift script test/plist/keyed.swift on macOS 27 (fixture built for this site, 2026-11-02)

Summary
Format          Binary plist (bplist00)
Size            1.0 KB
Object table    135 objects · 2-byte offsets · 1-byte references
Top-level object  dict · 4 entries
Keyed archive   NSKeyedArchiver, $version 100000 · 44 archived objects · $top: root
Decoded in      6 ms

NSKeyedArchiver object graph (JSON)
{
  "root": {
    "id": "6F9619FF-8B86-D011-B42D-00C04FC964FF",
    "blob": {
      "data": "3q2+7w==",
      "bytes": 4
    },
    "count": 3,
    "url": "https://example.com/notes?id=1",
    "ratio": 0.5,
    "tags": [
      "alpha",
      "beta",
      "shared value"
    ],
    "title": "Fixture Notes",
    "nested": {
      "again": "shared value",
      …
    },
    "set": [
      "only"
    ],
    "enabled": true,
    "created": "2026-11-02T08:30:00.000Z",
    "nothing": null
  }
}

Archived classes (8)
NSDictionary    2
NSArray         1
NSDate          1
NSMutableArray  1
NSNull          1
NSSet           1
NSURL           1
NSUUID          1

What it reads

  • Binary plists (bplist00): every object type of Apple’s format (dictionaries, arrays, ASCII and UTF-16 strings, integers up to 128 bit, reals, booleans, dates, data, UIDs). Reference cycles and out-of-range offsets are rejected instead of looping.
  • XML plists, with or without the DOCTYPE. External entities and DTDs are never resolved.
  • Any extension: .plist, .bplist, binary .strings, PrivacyInfo.xcprivacy, a .savedState file, or bytes you exported from a database column.

What you see

  • Summary: binary or XML, the size, the bplist object table (object count, offset and reference sizes), the top-level type and its entry count, and whether the file is a keyed archive.
  • XML plist: written the way plutil -convert xml1 writes it (tab indent, dictionary keys sorted, base64 data in 68-column lines, dates in UTC without fractions), so you can diff it against a Mac’s output.
  • JSON: dates as ISO 8601 strings, data as { "data": base64, "bytes": n }, integers above 2^53 as strings. plutil -convert json refuses any plist that contains a date or data; this view does not.
  • NSKeyedArchiver object graph: when $archiver is NSKeyedArchiver, every CF$UID reference is followed from $top. NSArray, NSSet and NSOrderedSet become arrays, NSDictionary a dictionary, NSString a string, NSData data, NSDate a date, NSUUID a UUID string, NSURL its string, NSNull null. Any other class (your own model objects, NSAttributedString, NSColor) is kept as a dictionary with its $class name and its keys, references resolved. An object that refers back to one still being decoded becomes { "$ref": n }, the index in $objects. A table lists how many objects of each class the archive holds.

Each view has Download and Copy buttons. Very large outputs show their first 1 MiB on the page; Download saves all of it.

How we checked it

The fixtures are in test/plist/ and rebuilt by test/plist/build.sh on macOS 27: a hand-written XML plist with every type converted to binary by plutil -convert binary1, and an NSKeyedArchiver archive written by Foundation’s NSKeyedArchiver.archivedData from a Swift script, with an NSString shared by two containers. The unit tests check that this page’s XML equals plutil -convert xml1 byte for byte, and that the decoded graph matches the values the Swift script archived.

How your data stays private

  • The viewer runs in a separate frame served from tools.rea-reverse.com, and decoding runs in a Web Worker inside it, on your device. Nothing you drop is uploaded.
  • This page shows one ad, below the article. The ad script runs on rea-reverse.com; the frame is a different origin, so the browser does not let the ad script, or any other script on this page, read the file you drop or the results.
  • The frame’s Content Security Policy blocks every request its scripts could make (connect-src 'none', no third-party resources at all). The frame cannot open windows or navigate this page by itself. The only things it passes to this page are its height and the analytics events below.
  • Keys and values are shown as plain text. URLs inside the plist are not turned into links and are never requested.
  • Analytics events contain only coarse values: success or an error code, binary or XML, whether it was a keyed archive, a size bucket and a duration bucket. No keys, values or class names. The frame hands them to this page, which sends them. The full list is on the privacy page.
  • Prefer no ads and no analytics at all? Open the viewer on its own page.

How to verify this yourself

  1. Open your browser’s developer tools on the Network tab and load this page. Requests to tools.rea-reverse.com are the frame: its HTML, styles, fonts and scripts, and the worker script /_astro/plist-worker-*.js. Requests to Google domains are the ad on this page.
  2. Clear the log, then drop a plist. Type domain:tools.rea-reverse.com in the filter box. At most the worker script /_astro/plist-worker-*.js appears again (a fresh worker is prepared for the next run); no request carries your data. Ad requests may continue, but they come from this page and carry nothing from the frame.
  3. Click the plist-viewer/ document from tools.rea-reverse.com and open Headers: its Content-Security-Policy has connect-src 'none', and frame-ancestors https://rea-reverse.com. In Chrome, Application > Frames also shows the frame’s origin and policy. The worker runs under default-src 'none'; script-src 'self'; connect-src 'none'.
  4. Check the isolation: in the Console, with the context set to top, run document.getElementById('tool-frame').contentDocument. It returns null. frames[0].document throws a SecurityError. That is what the ad script gets too.
  5. Or load the page, go offline, and drop the file. It still works.
  6. On a Mac, compare with plutil -convert xml1 -o - file.plist.

Limitations

  • Read only: it does not write binary plists or edit values.
  • Only bplist00. The bplist15 / bplist16 formats some Apple frameworks use internally are rejected with a clear message.
  • A real number with no fractional part (an NSDate stored as 815301000.0) is written as <integer> in the XML view, because the parser keeps one number type. plutil writes <real>. Values are the same.
  • Classes outside Foundation’s basic set are not interpreted: you see their keys, not what the class does with them.

Command-line equivalent

On a Mac, plutil turns the same archive into XML, but the object graph stays a flat $objects array linked by CF$UID numbers (macOS 27, trimmed):

$ plutil -convert xml1 -o - keyed.bplist
<?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>$archiver</key>
	<string>NSKeyedArchiver</string>
	<key>$objects</key>
	<array>
		<string>$null</string>
…
			<key>NS.time</key>
			<real>815301000</real>
		</dict>
		<dict>
			<key>$classes</key>
			<array>
				<string>NSDate</string>
				<string>NSObject</string>
			</array>
			<key>$classname</key>
			<string>NSDate</string>
		</dict>
…
	<key>$top</key>
	<dict>
		<key>root</key>
		<dict>
			<key>CF$UID</key>
			<integer>1</integer>
		</dict>
	</dict>
	<key>$version</key>
	<integer>100000</integer>
</dict>
</plist>

For a plain binary plist, plutil -p prints a readable dump (the test/plist/settings.bplist fixture, trimmed):

$ plutil -p settings.bplist
{
  "Big" => 9007199254740993
  "Blob" => {length = 81, bytes = 0x000102030405060708090a0b0c0d0e0f ... 494a4b4c4d4e4f50}
  "CFBundleIdentifier" => "com.example.fixture"
  "CFBundleShortVersionString" => "2.5.0"
  "Count" => 42
  "Created" => 2026-11-02 08:30:00 +0000
…
  "Greeting" => "Grüße, 你好 & <tags>"
…
}

JSON output from plutil stops at the first date or data value:

$ plutil -convert json -o - settings.bplist
settings.bplist: Invalid object in plist for JSON format

FAQ

How do I view a binary plist online without uploading it?

Drop it on this page. The file is decoded in a Web Worker in your browser that has no network access, so it is never sent anywhere. On a Mac, plutil -p file.plist prints the same values.

How do I convert a bplist to XML?

Drop the file and use the XML plist view: Download saves it as .xml.plist. The output follows plutil -convert xml1 (sorted keys, tab indent, 68-column base64), so it diffs cleanly against a Mac's output.

How do I decode an NSKeyedArchiver archive?

Drop it here. When $archiver is NSKeyedArchiver, the page follows every CF$UID from $top and shows the result as JSON: arrays, dictionaries, strings, dates, UUIDs and URLs instead of $objects indexes. Unknown classes keep their $class name and keys.

Why does plutil -convert json fail on my plist?

JSON has no date or binary data type, so plutil refuses any plist that contains <date> or <data> with "Invalid object in plist for JSON format". This page writes dates as ISO 8601 strings and data as base64 instead.

Does it support bplist15 or bplist16?

No. Only bplist00, the format plutil and NSPropertyListSerialization write, is read. Files starting with another bplist version get an error message rather than a wrong result.

Updated