I've seen a number of people get surprised by Xcode Cloud's handling of Bundle IDs. This post serves as a distillation of some of the advice I've given out over the years.
Bundle IDs Are The Primary Method Of Organization
A bundle ID is usually a reverse-FQDN (Fully Qualified Domain Name, eg com.companyname.app), that uniquely identifies a single app target within the Apple ecosystem. Bundle IDs are globally unique, so the Apple App Stores will never have more than one app with the same bundle IDs. Bundle IDs are also pretty much unchangeable. This is why the X (Formerly Twitter) app's bundle ID is still com.atebits.Tweetie2. When the app was first created, it was made by a totally different company, and it can't be changed, or else everyone would have to migrate to a different app.
It's this uniqueness and permanence that makes the bundle ID a perfect identifier for Xcode Cloud. When you open up a project within Xcode and onboard it, Xcode Cloud permanently associates the bundle ID of the target you're adding with the workflows and builds that will come after.
This is a very key point, because it's different from a set of tools like Fastlane, in that Xcode Cloud is NOT reading your bundle ID from the project file on every build and using that. It already knows your bundle ID, so it WILL use that every time it builds the project within this 'App'
Other levels of organisation
So in Xcode Cloud, the levels of organisation are:
Developer Team
|
--App (represented by the bundle ID)
|
--Workflow
|
-- Build
Under this app, all the Workflows and Builds fall under this bundle ID, and can't be accessed in the context of any other bundle ID.
Consequences For White-Label Apps
It's common enough for developers to create one generic version of an app, then sell that to other companies or organizations with a layer of branding applied. This is called a white-label app.
On a custom iOS CI system like Fastlane, you might run a process that builds the same app multiple times. You would have a script to switch out art assets and configuration strings before each build, and end up with a folder full of apps that were the same-but-different.
Each of these apps must have a different bundle ID, like so:
- com.companyA.app
- com.companyB.app
- com.companyC.app
Your company probably wouldn't submit these apps to Apple under your company's Apple Developer Account; instead, you would have access to the developer accounts for all of your customers, A, B and C.
At this point, you might have a script that runs through the folder and uploads each of those apps to the correct company's App Store account, from where they can be submitted independently to Apple.
However, because Xcode Cloud keys so heavily on bundle ID, we can't use a similar strategy here. Instead, we have to turn things upside down.
How To Set Up A White Label App On Xcode Cloud
Tl;dr - Onboard each bundle ID separately, and ensure that you set the correct bundle ID in the project once it's been cloned from the repo.
In order to onboard a White Label app, you'll have to ensure a couple of things:
- You should have access to each of your clients Development teams; In order to onboard, you need to be App Manager or above
- You should be or have access to the Org Admins for your repository; You'll need to install the Xcode Cloud Github Application, that will allow your repo to be accessed by Xcode Cloud
- Be signed in to Xcode with the Apple Accounts from your various customers
1. Onboard a project to Xcode Cloud
In Xcode 26, click on the 'Integrate->Create Workflow' menu option. You'll see a target chooser, and Xcode Cloud will pull your bundle ID from the project file that houses the target.
Go through all the dialogs, and get the project onboarded. If the bundle ID set in the project file has not been modified from the one in your repo, the build should complete successfully.
2. Change the Bundle ID, and onboard again
You may have to restart Xcode once you change the bundle ID. But once you have done this, you should be able to onboard the app AGAIN, but with a different bundle ID.
Then, within Xcode Cloud (on the web UI), when you switch between teams, you'll see that BOTH apps have builds that run.
3. The Repo Problem
By now, if you've onboarded a few bundle IDs, you'll notice that most of them are failing, because their bundle IDs don't match what Xcode Cloud expects.
4. Custom Scripts To The Rescue
Thankfully, Xcode Cloud gives us a tool that allows us to do more or less whatever we want. So we can alter the bundle ID in our project file after it's cloned onto the build machine, and before it's built by Xcode Cloud.
Create a new folder entitled ci_scripts/ in the folder that contains your project file. Created a file inside called ci_post_clone.sh. Run
chmod +x ci_post_clone.sh
on the terminal to ensure that the file is executable.
Within the file, you want to plunk the bundle ID of your app into the project file, in just the right place, so that when Xcode Cloud builds, it will be with the correct file.
If you use XcodeGen or Tuist, you will probably already have this file, and are comfortable with preparing the project file in the post clone script.
If not, you could use a tool like xcodeproj to make the necessary modifications to your project file, or more specifically, to the project.pbxproj file within the project file.
When you edit the project.pbxproj, you need to replace the lines that have
PRODUCT_BUNDLE_IDENTIFIER = com.companyA.app;
with
PRODUCT_BUNDLE_IDENTIFIER = com.companyB.app;
If you do this correctly in the post clone script, then the rest of the build will proceed as expected.
The trick will be to add the bundle ID of the company in Xcode Cloud's custom environment variables, so that they can be accessed in the post clone script.
Rinse, Repeat
You will now have to repeat this process for each version of the white label app you have. Bear in mind that this means you will have the same app being built on a number of different teams' Xcode Cloud accounts; it may be better to keep your Test Action for your own account, and only run Archive actions for TestFlight in your customer's accounts once these are done.
Once you've done that, any commit that starts an Xcode Cloud build will do so on multiple team accounts - one for each team you've onboarded to.
You'll have to configure the workflows for each team separately, so I recommend keeping your testing on your personal team, and only having Archive Actions on your customer's accounts, with a Post-Action for sending to App Store Connect.