Library / SDK
tuist/XcodeProj avatar
tuist/XcodeProj

XcodeProj: a Swift library for reading and rewriting .xcodeproj files

Project brief: Read, update and write your Xcode projects. XcodeProj XcodeProj is a library written in Swift for parsing and working with Xcode projects.

2,224 stars355 forksSwiftMIT

At a glance

What is it?
XcodeProj parses the pbxproj graph that Xcode writes, exposes it as Swift objects, and writes it back. It is aimed at automation authors, and the trade-off is that you own the correctness of every edit.
Who is it for?
Adopt XcodeProj if you are writing Swift tooling that has to edit project files programmatically, for example keeping CURRENT_PROJECT_VERSION in sync with a git tag, and you are prepared to diff every write. Do not adopt it if you only need to open a project in Xcode, or if your team already standardises on Ruby, where the CocoaPods Xcodeproj gem is the closer fit.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem XcodeProj solves: .xcodeproj files are not meant to be edited by hand

A .xcodeproj is a directory, not a single file. Inside it sits a pbxproj file that describes targets, build configurations, file references and the build phases that connect them, and every object in that graph is addressed by a generated identifier. Xcode writes the file, Xcode reads it, and the format is not something Apple documents as a stable public interface. That makes scripted edits awkward: adding a file to a target means creating the right object, wiring it into the right build phase, and keeping the identifiers consistent.

XcodeProj exists so that Swift code can do that work through types instead of string surgery. The README describes it as "a library written in Swift for parsing and working with Xcode projects", and notes that it is heavily inspired by CocoaPods XcodeProj and by the JavaScript xcode package. The audience is narrow and specific: people who build automation around Xcode projects. The README's list of projects using it is the clearest statement of who that is, and it includes Tuist, XcodeGen, Sourcery, Rugby, rules_xcodeproj and ProjLint. Those are all tools that generate or mutate projects rather than tools that consume them for display.

How XcodeProj models a project: XcodeProj, pbxproj and build configurations

The entry point is an XcodeProj value constructed from a path. From there the object graph is reachable in a shape that mirrors the file: the README's scripting example reaches into xcodeproj.pbxproj.buildConfigurations and iterates over configurations, checking and assigning entries in each configuration's buildSettings dictionary. That is the core data flow. You load the project, walk to the object you care about, mutate a property, and write the project back out.

The design choice worth noticing is that XcodeProj is a model, not a policy engine. It does not know that changing CURRENT_PROJECT_VERSION in one configuration and not another is probably a mistake, and it does not validate that a file reference you added points at something that exists. The README example handles that itself with a where clause that skips configurations where the key is absent. Any invariant your team cares about has to be expressed in your code, because the library will faithfully serialise whatever graph you hand it. That is the right split for a parser, but it means the library cannot be a safety net.

Installing XcodeProj with Swift Package Manager

The README documents two installation paths. The first is Swift Package Manager. You add the package as a dependency and list XcodeProj as a target dependency, as in the snippet the README gives:

swift
let package = Package(
    name: "myproject",
    dependencies: [
        .package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "8.12.0")),
    ],
    targets: [
        .target(
            name: "myproject",
            dependencies: ["XcodeProj"]),
        ]
)

Note the version constraint in the README: it pins to an 8.x range with .upToNextMajor(from: "8.12.0"). The repository's recent releases are in the 9.x line, the latest listed being 9.16.0, so the README example is behind what the release list shows. Check the version you actually resolve rather than copying that string unchanged. After adding the dependency, the target can import XcodeProj and the types above become available.

A first real use: syncing CURRENT_PROJECT_VERSION with a git tag using swift-sh

The second documented path is scripting through swift-sh, which lets a single Swift file declare its own dependencies and run as a script. The README's example keeps a project's version key in sync with a git tag. The script takes a project path and a new version, opens the project, and writes the version into every build configuration that already defines the key:

swift
#!/usr/bin/swift sh
import Foundation
import XcodeProj  // @tuist ~> 8.8.0
import PathKit

let projectPath = Path(CommandLine.arguments[1])
let newVersion = CommandLine.arguments[2]
let xcodeproj = try XcodeProj(path: projectPath)
let key = "CURRENT_PROJECT_VERSION"

for conf in xcodeproj.pbxproj.buildConfigurations where conf.buildSettings[key] != nil {
    conf.buildSettings[key] = newVersion
}

The script is stored in the repository, for example at scripts/set-project-version, and run against a project directory:

bash
$ scripts/set-project-version ./App.xcodeproj 1.2.3
$ git add App.xcodeproj
$ git commit -m "Bump version"
$ git tag 1.2.3

What you should see is a modified pbxproj inside App.xcodeproj and a clean commit. The README also notes that the version is passed in by hand and suggests that determining it automatically would be a natural next step, recommending a library that provides a Version object. It does not document a rollback path, so treat the git commit as your undo.

Where XcodeProj is the wrong tool

Writing a project file is not a safe operation by default. A round trip through a parser and serialiser can reorder or reformat parts of the pbxproj, and because the file is checked into git, that noise lands in review. The README does not document a round-trip stability guarantee, and it does not document rollback. If your workflow is one person occasionally adding a file through Xcode's UI, XcodeProj adds a dependency and a diff to review for no benefit.

The other clear boundary is language. XcodeProj is Swift. If your tooling is Ruby, the README points at CocoaPods XcodeProj as the inspiration, and that project is the natural choice in that ecosystem. Choosing the Swift library means your automation has to build in a Swift toolchain, which is a real constraint in CI environments that are otherwise Ruby or Node based. And if your goal is to stop hand-editing project files altogether rather than to edit them from code, the projects listed in the README such as Tuist and XcodeGen approach the problem from the other direction: you describe the project in a manifest and the tool generates the pbxproj. XcodeProj is the layer underneath that, not a replacement for it.

How XcodeProj differs from the CocoaPods Xcodeproj gem

The closest alternative is the Ruby gem, CocoaPods Xcodeproj. Both parse and write the same file format, and XcodeProj's README states the Swift library was heavily inspired by it, so the object model will feel familiar if you have used the gem. The difference is the runtime and the audience. The gem lives in the Ruby ecosystem that CocoaPods built, so it drops into a Gemfile and a Rakefile without a Swift toolchain. XcodeProj lives in Swift, which means it can be imported directly by the tools that already compile Swift, and the README's list of users is dominated by exactly those: Tuist, XcodeGen, Sourcery, Rugby, rules_xcodeproj. If your automation is a Swift package or a swift-sh script, importing a gem is not an option, and if your automation is a Rake task, adding a Swift build step is a cost. The choice is usually made by the language your tooling already speaks rather than by a feature difference.

Licence, release cadence and the cost of upgrading

XcodeProj is released under the MIT licence, and the README links to LICENSE.md in the repository. MIT is permissive, so the practical implication is that you can depend on it in a closed-source tool, but the usual obligation to preserve the licence notice travels with the source you redistribute. That is a general property of the licence, not advice about your situation.

On maintenance, the repository is not archived and the most recent push recorded is 2026-08-20, the same date as the 9.16.0 release. The release list shows 9.15.0 on 2026-07-28, 9.15.1 on 2026-08-11 and 9.16.0 on 2026-08-20, so the project is moving. The upgrade cost is the part to plan for. The README's own install snippet still shows an 8.x constraint while releases are at 9.x, which is a reminder that major versions happen and that a pinned .upToNextMajor constraint will not carry you across one. The repository carries a CHANGELOG.md and a cliff.toml, and the README documents no migration guide, so the changelog is where you check what a major bump changes before you take it.

Editorial conclusion

Adopt XcodeProj if you are writing Swift tooling that has to edit project files programmatically, for example keeping CURRENT_PROJECT_VERSION in sync with a git tag, and you are prepared to diff every write. Do not adopt it if you only need to open a project in Xcode, or if your team already standardises on Ruby, where the CocoaPods Xcodeproj gem is the closer fit. Before committing to it, verify two things in your own repository: that the version you pin in Package.swift resolves, and that a round trip through XcodeProj on your largest .xcodeproj produces a diff you can explain line by line.

Frequently asked questions

What is the difference between an xcodeproj and an xcworkspace?

The README does not cover workspaces. It describes XcodeProj as a library for parsing and working with Xcode projects, and the scripting example opens a single path such as ./App.xcodeproj.

How do I install XcodeProj?

The README documents two routes: adding the package to your Package.swift dependencies and listing XcodeProj as a target dependency, or using swift-sh to declare the dependency inside a script with a comment such as // @tuist ~> 8.8.0.

What is an xcodeproj file?

It is the Xcode project that XcodeProj reads and writes. The README's example passes a project path to XcodeProj(path:) and then reaches into xcodeproj.pbxproj.buildConfigurations, so the pbxproj inside the project directory is where targets and build settings live.

What is an Xcode project?

In XcodeProj's model it is the object returned by XcodeProj(path:), which exposes a pbxproj containing build configurations and their buildSettings dictionaries. The README treats it as the unit you load, mutate and write back.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tuist-xcodeproj.svg)](https://hysenlabs.com/projects/tuist-xcodeproj)