Warning
This support is experimental. Xcode 27.2 is the first release that writes the format, so the mapping described here has only been verified against Apple's own library and hand written projects, not against a corpus of real Xcode output. The API and the conversion details may still change, and a converted project is worth checking before you commit it.
Xcode 27.2 can store a project as JSON in project.xcproj instead of the property list in
project.pbxproj. You turn it on in the file inspector, and projects that use it still open in
earlier Xcode 27 releases. Apple documents and implements the format in
apple/xcode-project-format.
XcodeProj reads and writes both formats through the same PBXProj object graph, so code that
already uses this library keeps working unchanged.
XcodeProj(path:) looks for project.pbxproj first and falls back to project.xcproj. The format
it found is available as projectFormat.
let project = try XcodeProj(path: "MyApp.xcodeproj")
print(project.projectFormat) // .pbxproj or .xcproj
print(project.pbxproj.rootObject?.targets.map(\.name) ?? [])write(path:) uses the format the project was read in, so an unchanged project is written back
byte for byte. Pass format: to convert.
// Keep whatever format the project already uses.
try project.write(path: "MyApp.xcodeproj")
// Convert an existing project to JSON.
try project.write(path: "MyApp.xcodeproj", format: .xcproj)
// Convert back.
try project.write(path: "MyApp.xcodeproj", format: .pbxproj)Writing removes only the file it produces, so converting leaves the old file behind. Delete it yourself, or write to a fresh directory.
PBXProj exposes the conversion directly when you do not need the surrounding bundle.
let proj = try PBXProj(xcprojPath: "MyApp.xcodeproj/project.xcproj")
let data = try proj.xcprojData()The JSON format prefers name based references such as "App/compile-sources" over the UUIDs the
property list uses, which is most of what makes its diffs readable. XcodeProj writes an identifier
only where the format requires one, or where a name would be ambiguous, which matches what Xcode
itself produces.
Pass .preserveAll when you are migrating a project and want every existing UUID to survive the
conversion. The file gets considerably noisier, so this is meant for one-off migrations and
debugging rather than day to day use.
try project.write(
path: "MyApp.xcodeproj",
format: .xcproj,
xcprojOutputSettings: XCProjOutputSettings(objectIDPolicy: .preserveAll)
)Objects that arrive without an identifier get the same deterministic identifiers a generated
project would get, so converting to project.pbxproj produces stable output.
project.xcproj keeps one flat dictionary of build settings per project and per target, and marks
the values that differ between build configurations with a config= condition. PBXProj keeps one
dictionary per XCBuildConfiguration. XcodeProj moves that condition in and out of the key as it
converts.
project.xcproj |
project.pbxproj |
|---|---|
"SDKROOT": "iphoneos" |
SDKROOT in every configuration |
"ONLY_ACTIVE_ARCH[config=Debug]" |
ONLY_ACTIVE_ARCH in the Debug configuration |
"FLAGS[config=Debug][sdk=ios*]" |
FLAGS[sdk=ios*] in the Debug configuration |
A config= condition that names no configuration, such as the wildcard config=*, has no single
configuration to move to. It stays on the key and applies to every configuration.
The two formats hold the same project model, but the property list carries a few values that the
JSON schema has no place for. Converting a project.pbxproj to project.xcproj drops them.
- The
nameof a file reference when it differs from the last component of its path. This is most visible on the children of a variant group, where Xcode uses the language as the name and derives it from the.lprojdirectory in the path instead. lastKnownFileType, which is Xcode's own guess from the file extension. An explicitexplicitFileTypeis kept.- The editor settings of a file element:
usesTabs,indentWidth,tabWidthandwrapsLines. productNameon a target,buildActionMaskon a build phase andisEditableon a build rule.compatibilityVersion,projectDirPath,projectRoot,hasScannedForEncodingsanddefaultConfigurationIsVisible.- The order of files inside a build phase. The JSON format records each membership on the file, so the order is rebuilt by walking the groups and files tree.
archiveVersion,objectVersionandpreferredProjectObjectVersion, which the JSON format does not store. A project read fromproject.xcprojgets object version 77.CreatedOnToolsVersionandBuildIndependentTargetsInParallelfrom the project attributes. The second one only records the default, so the behaviour is unchanged.- The identifiers of the objects that exist only in the property list model: container item proxies,
target dependencies, Swift package references and the exception sets of a synchronized folder. The
JSON format expresses those relations inline, so there is nothing to hang an identifier on, and
even
.preserveAllregenerates them. The identifiers they point at, such as a remote target or an imported product, are kept.
Converting in the other direction keeps everything the property list can hold, with two caveats.
required-capabilitieshas no place inPBXProj, so a project written back toproject.xcprojloses the entries it arrived with. A capability this version of Apple's library does not recognise stops the read instead, with an error telling you which Xcode feature the project needs.- A single
platformFilteron a build file or dependency comes back as the pluralplatformFilterslist Xcode uses today. The meaning is the same.
A few things exist in one format and not the other. Rather than convert them to something close,
XcodeProj raises XCProjError so the gap is visible.
- The
apple-scriptandjava-archivebuild phases have noPBXProjtype. - The
code-generation-visibilityanddecompressbuild file attributes have no establishedproject.pbxprojspelling, and anATTRIBUTEStoken this library does not know is rejected rather than guessed at. The known tokens arePublic,Private,Weak,CodeSignOnCopy,RemoveHeadersOnCopy,Client,Serverandno_codegen. - Copy files destinations outside the ten Xcode offers in its build phase editor, such as the headers directories and the Info.plist file.
- The
preserveline ending style. - Asset tags in a synchronized folder's exception sets, and platform filters in its build phase exception sets, which the property list exception set types have no field for.
- A name based reference that matches no element, or more than one.
Apple's package needs Swift 6.1, macOS 14 and iOS 17, so XcodeProj now requires the same.