The fastest and most accurate parser for Xcode project files (.pbxproj). 10-28x faster than alternatives (xcode, XcodeProj, xcodeproj) with better error messages and full spec compliance.
bun add @bacons/xcode
Performance
Run benchmarks with bun run bench or bun run bench:compare for cross-language comparison.
Parser
Language
Time (29KB)
Time (263KB)
Relative
@bacons/xcode
TypeScript
0.15ms
0.81ms
1x
xcode (legacy)
JavaScript
1.54ms
crashes
10x slower
XcodeProj (Tuist)
Swift
2.00ms
11.2ms
13x slower
xcodeproj (CocoaPods)
Ruby
3.63ms
22.5ms
24x slower
Key Performance Features
10-28x faster than alternatives (xcode, XcodeProj, xcodeproj)
Single-pass parsing with no intermediate representation
Pre-computed lookup tables for character classification
Handles files that crash the legacy parser
Here is a diagram of the grammar used for parsing:
Why
The most popular solution for parsing pbxproj files is a very old package by Cordova called xcode.
But xcode has some major issues:
Inaccurate parsing: strings can be quoted incorrectly very often, lists often don't work.
Outdated: values for App Clips, iMessage Sticker packs, etc are missing.
Untyped: TypeScript is a crutch I proudly support.
Notice how you don't need to strip or reapply quotes, you also don't need to filter out comments because the default visitor ignores comments in favor of regenerating them dynamically like Xcode does.
API
There's a mutable-graph layer which makes it much easier to work with pbxproj.
import {
PBXAggregateTarget,
PBXFrameworksBuildPhase,
PBXLegacyTarget,
PBXNativeTarget,
XcodeProject,
} from"@bacons/xcode";
const project = XcodeProject.open("/path/to/project.pbxproj");
// Get all targets:
project.rootObject.props.targets;
Create a Swift file:
import { PBXBuildFile, PBXFileReference } from"@bacons/xcode";
import path from"path";
// Get `project` from XcodeProject.const file = PBXBuildFile.create(project, {
fileRef: PBXFileReference.create(project, {
path: "MyFile.swift",
sourceTree: "<group>",
}),
});
// The file and fileRef will now be injected in the pbxproj `objects` dict.
XCScheme Support
Parse and manipulate Xcode scheme files (.xcscheme). Schemes define how targets are built, run, tested, profiled, analyzed, and archived.
Low-level API
import * as scheme from"@bacons/xcode/scheme";
import fs from"fs";
// Parse an xcscheme fileconst xml = fs.readFileSync(
"/path/to/Project.xcodeproj/xcshareddata/xcschemes/App.xcscheme",
"utf-8",
);
const xcscheme = scheme.parse(xml);
// Modify the scheme
xcscheme.buildAction.parallelizeBuildables = true;
xcscheme.launchAction.environmentVariables = [
{ key: "DEBUG", value: "1", isEnabled: true },
];
// Serialize back to XMLconst outputXml = scheme.build(xcscheme);
fs.writeFileSync("/path/to/App.xcscheme", outputXml);
High-level API
import { XcodeProject, XCScheme } from"@bacons/xcode";
// Load project and get schemesconst project = XcodeProject.open("/path/to/project.pbxproj");
const schemes = project.getSchemes();
const appScheme = project.getScheme("App");
// Create a new schemeconst newScheme = XCScheme.create("MyNewScheme");
newScheme.addBuildTarget({
buildableIdentifier: "primary",
blueprintIdentifier: "ABC123", // Target UUIDbuildableName: "App.app",
blueprintName: "App",
referencedContainer: "container:Project.xcodeproj",
});
newScheme.setLaunchEnvironmentVariable("API_URL", "https://api.example.com");
newScheme.addLaunchArgument("-verbose");
// Save to disk
project.saveScheme(newScheme);
// Create scheme for an existing targetconst mainTarget = project.rootObject.props.targets[0];
const targetScheme = project.createSchemeForTarget(mainTarget);
project.saveScheme(targetScheme);
Scheme Management
Parse and build xcschememanagement.plist files that control scheme visibility and ordering:
Parse and manipulate Xcode workspace files (.xcworkspace). Workspaces group multiple Xcode projects together, commonly used with CocoaPods, monorepos, and multi-project setups.
Low-level API
import * as workspace from"@bacons/xcode/workspace";
import fs from"fs";
// Parse a workspace fileconst xml = fs.readFileSync(
"/path/to/MyApp.xcworkspace/contents.xcworkspacedata",
"utf-8",
);
const ws = workspace.parse(xml);
// Access project referencesconsole.log(ws.fileRefs); // [{ location: "group:App.xcodeproj" }, ...]// Modify the workspace
ws.fileRefs?.push({ location: "group:NewProject.xcodeproj" });
// Serialize back to XMLconst outputXml = workspace.build(ws);
fs.writeFileSync("/path/to/contents.xcworkspacedata", outputXml);
High-level API
import { XCWorkspace } from"@bacons/xcode";
// Open an existing workspaceconst workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
// Get all project pathsconst projects = workspace.getProjectPaths();
console.log(projects); // ["group:App.xcodeproj", "group:Pods/Pods.xcodeproj"]// Check if workspace contains a projectif (!workspace.hasProject("NewProject.xcodeproj")) {
workspace.addProject("NewProject.xcodeproj");
}
// Remove a project
workspace.removeProject("OldProject.xcodeproj");
// Work with groupsconst group = workspace.addGroup("Libraries", "group:Libraries");
group.fileRefs?.push({ location: "group:Libraries/MyLib.xcodeproj" });
// Save changes
workspace.save();
// Create a new workspaceconst newWorkspace = XCWorkspace.create("MyWorkspace");
newWorkspace.addProject("App.xcodeproj");
newWorkspace.addProject("Pods/Pods.xcodeproj");
newWorkspace.save("/path/to/MyWorkspace.xcworkspace");
Location Types
Workspace file references use location specifiers:
group:path - Relative path from workspace root (most common)
Manage IDEWorkspaceChecks.plist files that store workspace check states. Introduced in Xcode 9.3, these files prevent Xcode from recomputing checks each time a workspace is opened.
The primary use is suppressing the macOS 32-bit deprecation warning:
import { XCWorkspace, IDEWorkspaceChecks } from"@bacons/xcode";
// Suppress the 32-bit deprecation warningconst workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
workspace.setMac32BitWarningComputed();
// Or work with IDEWorkspaceChecks directlyconst checks = IDEWorkspaceChecks.openOrCreate("/path/to/MyApp.xcworkspace");
checks.mac32BitWarningComputed = true;
checks.save();
// Low-level APIimport * as workspace from"@bacons/xcode/workspace";
const plist = workspace.parseChecks(plistString);
console.log(plist.IDEDidComputeMac32BitWarning); // trueconst output = workspace.buildChecks({ IDEDidComputeMac32BitWarning: true });
XCConfig Support
Parse and manipulate Xcode configuration files (.xcconfig). These files define build settings that can be shared across targets and configurations.
// Required include - throws if file not found
#include "Base.xcconfig"
// Optional include - silently ignored if file not found
#include? "Optional.xcconfig"
Variable Expansion
XCConfig supports variable references and the $(inherited) keyword:
// Reference other settings
PRODUCT_BUNDLE_IDENTIFIER = $(BUNDLE_ID_PREFIX).$(PRODUCT_NAME:lower)
// Inherit from included files
OTHER_LDFLAGS = $(inherited) -framework UIKit
XCSharedData Support
Access and manipulate shared data directories (xcshareddata) which contain schemes, breakpoints, and workspace settings that are intended for version control.
High-level API
import { XcodeProject, XCSharedData } from"@bacons/xcode";
// Get shared data from a projectconst project = XcodeProject.open("/path/to/project.pbxproj");
const sharedData = project.getSharedData();
// Access schemesconst schemes = sharedData.getSchemes();
const appScheme = sharedData.getScheme("App");
// Access breakpointsif (sharedData.breakpoints) {
console.log(sharedData.breakpoints.breakpoints?.length);
}
// Access workspace settingsif (sharedData.workspaceSettings) {
console.log(sharedData.workspaceSettings.PreviewsEnabled);
}
// Modify and save
sharedData.workspaceSettings = {
PreviewsEnabled: true,
IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded: false,
};
sharedData.save();
Breakpoints API
Parse and build Xcode breakpoint files (Breakpoints_v2.xcbkptlist):
import * as breakpoints from"@bacons/xcode/breakpoints";
import fs from"fs";
// Parse breakpoint fileconst xml = fs.readFileSync(
"/path/to/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist",
"utf-8",
);
const list = breakpoints.parse(xml);
// Access breakpointsfor (const bp of list.breakpoints ?? []) {
console.log(bp.breakpointExtensionID); // "Xcode.Breakpoint.FileBreakpoint"console.log(bp.breakpointContent?.filePath);
console.log(bp.breakpointContent?.startingLineNumber);
}
// Add a new breakpoint
list.breakpoints?.push({
breakpointExtensionID: "Xcode.Breakpoint.FileBreakpoint",
breakpointContent: {
uuid: "new-uuid",
shouldBeEnabled: true,
filePath: "MyApp/ViewController.swift",
startingLineNumber: "42",
endingLineNumber: "42",
actions: [
{
actionExtensionID: "Xcode.BreakpointAction.DebuggerCommand",
actionContent: { consoleCommand: "po self" },
},
],
},
});
// Serialize back to XMLconst outputXml = breakpoints.build(list);
Workspace Settings API
Parse and build workspace settings files (WorkspaceSettings.xcsettings):
The parsing is very simple (simplicity is the key).
pbxproj is an "old-style plist" (or ASCII Plist), this means it should be possible to represent it as any other static configuration file type like JSON or XML.
We support the following types: Object, Array, Data, String. Notably, we avoid dealing with Integer, Double, Boolean since they appear to not exist in the format.
TODO
Reading.
Writing.
Escaping scripts and header search paths.
Generating UUIDs.
Reference-type API.
Build setting parsing.
xcscheme support.
Benchmarks (bun run bench).
xcworkspace support.
XCConfig Parsing: .xcconfig file parsing with #include support and build settings flattening.
XCSharedData: Shared project data directory (schemes, breakpoints, workspace settings).
XCSchemeManagement: Scheme ordering, visibility, and management plist.
WorkspaceSettings: (xcshareddata/WorkspaceSettings.xcsettings) Derived data location, build system version, auto-create schemes setting.
IDEWorkspaceChecks: (xcshareddata/IDEWorkspaceChecks.plist) Workspace check state storage (e.g., 32-bit deprecation warning).
Docs
Docs are in the works. For now, you can refer to the types and the estimated pbxproj spec.
The API will change in the future, for now we have two methods:
import {
/** Given a stringified `pbxproj`, return a JSON representation of the object. */
parse,
/** Given a JSON representation of a `pbxproj`, return a `.pbxproj` string that can be parsed by Xcode. */
build,
} from"@bacons/xcode/json";
import fs from"fs";
import path from"path";
const pbxproj = parse(fs.readFileSync("/path/to/project.pbxproj"));
const pbxprojString = build(pbxproj);
PBXVariantGroup is a localized PBXGroup.
File Path Resolution
Files will have an attribute sourceTree which indicates how the file path should be resolved.
BUILT_PRODUCTS_DIR: Paths are relative to the built products directory.
DEVELOPER_DIR: Paths are relative to the developer directory.
Indicates that the path "multitarget/AppDelegate.m" is relative to sourceTree "". We need to check the containing PBXGroup's path (only defined when the group is linked to a directory in the file system). Groups can live inside of other groups so this process is recursive.
Versioning
Certain values loosely map to each other. For instance the top-level objectVersion (which indicates the versioning used for the objects in the top-level objects dictionary), maps to the rootObject -> PBXProject's compatibilityVersion string. Here is an up-to-date mapping (May 2022):
The npm package @bacons/xcode receives a total of 280,451 weekly downloads. As such, @bacons/xcode popularity was classified as popular.
We found that @bacons/xcode demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.It has 1 open source maintainer collaborating on the project.
Package last updated on 28 Feb 2026
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
OSV withdrew 157 OSV malware reports after automated false positives incorrectly flagged trusted npm and PyPI packages, sending bad records into tools that rely on OSV data.