plist.js

Parses and builds Apple property lists in XML, binary, and OpenStep formats for Node.js and browsers.

Library
npm
v5.0.0
607stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
61/100Good
Development Activity60
Maintenance32
Community72
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture75
Code Quality80
Innovation70
Learning Curve55

plist is a TypeScript library for parsing and building Apple property list (.plist) files across all three formats Apple has used historically: XML, binary (bplist00), and the legacy OpenStep/ASCII format used by macOS’s defaults read. Format detection is automatic, so a single parse() call handles whatever plist variant it’s handed, whether that’s an Xcode Info.plist, an iOS entitlements file, or output from a macOS command-line tool.

The library ships as pure TypeScript with full type declarations and is dual-published for Node.js and the browser: the Node build uses @xmldom/xmldom and xmlbuilder for XML handling, while the browser build swaps in the native DOMParser via conditional exports, dropping those dependencies entirely and shrinking to roughly 4 KB gzipped. It underpins plist handling across the Node/Cordova/Electron tooling ecosystem, and sees over 19 million downloads a week on npm.

What You Get

  • Format auto-detection - parse() inspects the input and dispatches to the XML, binary, or OpenStep parser without the caller needing to know the format ahead of time
  • Binary plist support - parseBinary()/buildBinary() implement the bplist00 binary format directly, including the offset table and object-reference sizing Apple’s format requires
  • OpenStep/ASCII parser - a hand-written recursive-descent parser (parseOpenStep()) for the legacy { key = value; } format produced by tools like defaults read
  • Browser-optimized build - conditional exports select a build that uses native DOMParser and string-based XML construction, so @xmldom/xmldom and xmlbuilder never ship to the browser bundle
  • Full TypeScript types - written in TypeScript with a PlistValue union type describing every plist value shape, plus generated .d.ts declarations

Common Use Cases

  • Reading and modifying iOS/macOS Info.plist files during a build or CI step
  • Parsing entitlements or configuration plists produced by Xcode or codesign
  • Building Cordova/Electron/React Native tooling that needs to generate or patch native app manifests
  • Round-tripping binary plists exported by macOS defaults or plutil without shelling out to native tools

Under The Hood

Architecture The library is organized as a thin flat module under src/, with index.ts re-exporting five independent entry points — parse, parseOpenStep, parseBinary, build, and buildBinary — that share only the PlistValue union type defined in parse.ts. Each format has its own self-contained parser/builder: parse.ts walks an XML DOM tree built by @xmldom/xmldom (or the browser’s native DOMParser, swapped in via conditional exports in parse.browser.ts/build.browser.ts) using a straightforward recursive parsePlistXML switch over node names; parse-binary.ts and build-binary.ts implement Apple’s bplist00 binary format directly against a DataView, including an object-flattening/deduplication pass (flattenObjects) on write and an offset-table walk on read; and parse-openstep.ts is a small hand-rolled recursive-descent OpenStepParser class for the legacy ASCII format. There’s no shared abstraction between the three formats beyond the PlistValue type, which keeps each parser easy to reason about in isolation but means format-specific fixes (like the __proto__ guard in parse.ts) aren’t automatically shared across the others. Format auto-detection lives entirely in the top-level parse() function, which peeks at the input’s type and prefix bytes before delegating.

Tech Stack The project is pure TypeScript (strict mode, ES2022 target, nodenext module resolution) with zero runtime dependencies in its browser build and only two small dependencies in the Node build — @xmldom/xmldom for DOM-based XML parsing and xmlbuilder for XML string construction — both selected via package.json’s conditional exports map so browser bundlers pull in neither. There’s no framework layer: the package is a library consumed via import { parse, build } from 'plist'. Tooling is minimal and modern — tsc alone handles the build (emitting dist/ with declarations and source maps), Vitest runs the test suite, and @changesets/cli drives versioned releases under a pnpm workspace with a pinned packageManager field. CI runs via GitHub Actions workflows in .github/workflows/.

Code Quality Tests live under test/ as four Vitest suites (parse.test.ts, parse-binary.test.ts, parse-openstep.test.ts, build.test.ts) plus binary and ASCII fixture files, and they’re substantive rather than smoke tests — parse.test.ts alone covers every plist scalar type, empty-node edge cases, CDATA and comment handling, emoji round-tripping through both XML and binary codecs, a regression fixture for a deep-nesting performance issue, and an explicit test asserting a __proto__ dict key is rejected. Error handling is explicit throughout via a small invariant() helper that throws descriptive errors rather than silently returning wrong data. TypeScript strict mode is enabled and the public API is fully typed via the PlistValue union, though the internal binary-format code leans on numeric bit-twiddling with light inline commenting rather than named constants for every magic number. No linter configuration was found in the repo root.

API Design The public surface is deliberately small and consistent: five verbs (parse, parseBinary, parseOpenStep, build, buildBinary) that all operate on the same PlistValue type, so a consumer never needs format-specific knowledge unless they want it — parse() alone auto-detects XML, binary, and OpenStep input and dispatches accordingly. Getting started requires zero configuration, and the README documents every function’s parameters and return type directly, including the BuildOptions shape for XML formatting. The main differentiator versus older plist libraries in the ecosystem is unifying all three plist formats behind one auto-detecting entry point with zero runtime dependencies in the browser — genuinely useful, but each format’s implementation follows the publicly documented Apple/NeXT specs rather than introducing new techniques.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search