White label apps
How to white-label your application using codemagic.yaml
These are the steps you need to get started white labeling your application using Codemagic.
- Add your base app to Codemagic
- Storing client’s assets somewhere Codemagic can access
- Create a unique environment variable group for each client (via UI or API)
- Setup your
codemagic.yaml
workflows to dynamically build for all clients - Start new builds via API, passing the client Id, and the environment variable group name
1. Add your base app to Codemagic
You don’t have to create a Codemagic application for each client you want to white-label for, only one application linked with your base code is required.
The apps you have available on Codemagic are listed on the Applications page. Click Add application to add a new app.
- If you have more than one team configured in Codemagic, select the team you wish to add the app to.
- Connect the repository where the source code is hosted. Detailed instructions that cover some advanced options are available here.
- Select the repository from the list of available repositories. Select the appropriate project type.
- Click Finish: Add application
2. Storing client assets
Instead of adding each customer’s assets to the base app repository, you should store the assets required for rebranding the app somewhere accessible by Codemagic. This could be an encrypted S3 or GCP storage bucket or a headless CMS. Each customer should be assigned a unique identifier which can be passed to the white-label workflow when the build is started.
You should create a zip archive for each client that uses their unique identifier in the file name, e.g. assets_001.zip
for client 001
. When a build is started for a specific customer’s app, this unique identifier will be sent in the API request payload and the correct asset archive will be downloaded and used to rebrand the app.
💡 The zip archive typically contains these folders:
android_assets/
. This folder contains the Android icons from/android/app/src/main/res/
.ios_assets/
. This folder contains the iOS icons from/ios/Runner/Assets.xcassets/AppIcon.appiconset/
.
Other assets such as fonts, images, etc. can also be added to this zip archive.
Avoid adding any sensitive files such as certificates, profiles, key stores, or other sensitive data in this archive.
3. Create a unique environment variable group for each client
During the white-label build, Codemagic uses client-specific data to set or replace various values in the base code and to sign and publish the app to the stores.
You should create a uniquely named environment variable group for each of your clients that contains secure environment variables for items such as certificates, profiles, API keys, or other client-specific credentials.
This group might contain the following environment variables:
- Android package name.
PACKAGE_NAME
. - Android Keystore information.
CM_KEYSTORE
(base64 encoded),CM_KEY_ALIAS
,CM_KEY_PASSWORD
,CM_KEYSTORE_PASSWORD
,CM_KEYSTORE_PATH
. - The content of the Google Cloud service JSON file to publish to Play Store.
GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
, learn how to get it here. - iOS app details.
APP_STORE_ID
,BUNDLE_ID
. - App Store Connect API key.
APP_STORE_CONNECT_KEY_IDENTIFIER
,APP_STORE_CONNECT_ISSUER_ID
,APP_STORE_CONNECT_PRIVATE_KEY
, learn how to create create a new key here. - iOS Distribution certificate private key.
CERTIFICATE_PRIVATE_KEY
, learn how to obtain it here. - .env file if your app uses some secrets at runtime.
DOTENV_FILE
(base64 encoded).
To add these values you can either use the Codemagic UI or use the Codemagic REST API to add these groups and values programmatically, which could be advantageous if you have a large number of clients or wish to add these values from your own backend system or client dashboard.
To add an environment variable using the Codemagic REST API, you need your API access token, the application id, the client group unique name, and the variable name and value.
- The access token is available in the Codemagic UI under Teams > Personal Account > Integrations > Codemagic API > Show. You can then store this as an environment variable if you are calling the REST API from other workflows.
- Once you have added your app in Codemagic, open its settings and copy the application id from the browser address bar -
https://codemagic.io/app/<APP_ID>/settings
- The client group name, is the group that holds all variables for this client e.g.
WL_001
,WL_002
.
An example of adding a secure variable to an application group looks like this:
curl -XPOST -H 'x-auth-token: <your-auth-token>' \
-H 'Content-Type: application/json;charset=utf-8' \
-d '{
"key": "<variable-name>",
"value": "<variable-value>"
"group":"<client-unique-group-name>",
"secure": true
}' \
'https://api.codemagic.io/apps/<app-id>/variables'
💡 Files such as Android keystores, or .env files should be base64 encoded and can be passed like this:
-d '{ "key": "<variable-name>", "value":
$(cat fileName | base64) ...
And then decode it during the build like this:
echo $VAR | base64 --decode > /path
4. Setup your codemagic.yaml workflows to dynamically build for all clients
Your codemagic.yaml file contains various workflows for building your app for all clients e.g. android-qa-workflow
, ios-release-workflow
.
In most cases, white label automation is done using shell scripts to perform tasks such as downloading assets, copying files such as logos, images, fonts, etc. to a new location, or changing string values in projects. Here you will find some common script samples we are using in our final sample project.
Downloading assets from Amazon S3
The Amazon CLI tools are pre-installed on Codemagic’s machines which makes it easy to store assets such as images, fonts, logos, etc. in an encrypted S3 bucket and then download these to the build machine when building each white label version.
The following is an example of downloading a zip archive from Amazon S3 during the build where the CLIENT_ID
variable is provided when the build is triggered using the Codemagic REST API:
environment:
groups:
- aws_credentials
vars:
S3_BUCKET_NAME: cmwhitelabel
CLIENT_ASSETS_FOLDER: client_assets
...
scripts:
- name: Get assets from AWS S3 bucket
script: |
aws s3 cp s3://$S3_BUCKET_NAME/assets_${CLIENT_ID}.zip assets.zip
unzip assets.zip -d $CLIENT_ASSETS_FOLDER
Use this script if you’re using a headless CMS instead:
scripts:
- name: Get assets from Contentful CMS
script: |
FILE_URL=$(curl --request GET --header "Authorization: Bearer $CONTENTFUL_API_TOKEN" "https://cdn.contentful.com/spaces/${CONTENTFUL_SPACE_ID}/environments/master/assets" | jq '.items[].fields' | jq -r --arg id "assets_$CLIENT_ID" '. | select (.title==$id) | .file.url' | cut -c 3-)
curl -H "Authorization: Bearer $CONTENTFUL_API_TOKEN" $FILE_URL --output assets.zip
Changing the Android package name
To natively change the Android package name, we can utilize a build automation tool like Gradle to modify the necessary files.
In this example, we’re first creating a file called changePackage.gradle
in the root directory of the Android project, which updates the applicationId
property of the defaultConfig block in your app’s build configuration, then adding this line to the app/build.gradle
file to apply the changePackage.gradle
script during the build process.
- name: Change Android package name
script: |
echo "android { defaultConfig { applicationId '${PACKAGE_NAME}' } }" > android/changePackage.gradle
echo "apply from: rootProject.file('changePackage.gradle')" >> android/app/build.gradle
Changing the iOS bundle ID
The automation scripts used in a white label workflow will often need to modify the content of a configuration file. This can be achieved using the sed
stream editor utility, which can perform basic text transformations such as replacing or adding text in a file.
For example, if you want to change the bundle identifier used in the Xcode project by modifying the project.pbxproj
file, the following script will set the PRODUCT_BUNDLE_IDENTIFIER
with a value stored in the environment variable called $BUNDLE_ID
.
- name: Set bundle id
script: sed -i '' -e 's/PRODUCT_BUNDLE_IDENTIFIER \= [^\;]*\;/PRODUCT_BUNDLE_IDENTIFIER = '${BUNDLE_ID}';/' ios/Runner.xcodeproj/project.pbxproj
Changing app name
Using sed
, the following script will replace the line in the AndroidManifest.xml
that starts with android:label=
with a new line contains the new app name $APP_NAME
.
- name: Change Android app name
script: sed -i.bak "s/android:label=.*/android:label=\"$APP_NAME\"/g" android/app/src/main/AndroidManifest.xml
PlistBuddy is a utility on macOS that can be used to perform operations on plist files. This approach can be used with native Swift/Objective-C apps, but please note that setting values directly in a Flutter project may cause problems with your project and you should consider using sed
instead.
- name: Change iOS app name
script: /usr/libexec/PlistBuddy -c "Set :CFBundleName $APP_NAME" -c "Set :CFBundleDisplayName $APP_NAME" ios/${XCODE_SCHEME}/Info.plist
Changing app icons
For Android apps, you should run a script to update the icons located in android/app/src/main/res
where you will find a number of directories that contain an icon for specific resolutions such as drawable-hdpi
, drawable-mdpi
, drawable-xhdpi
, drawable-xxhdpi
, drawable-xxxhdpi
. Your script to update the icons in your Android project might look something like this:
name: Change Android app icons
script: cp -r ./$CLIENT_ASSETS_FOLDER/android_assets/* ./android/app/src/main/res
For iOS apps, if you look at an Xcode project using Finder, you will see that the icons added in Xcode are located in <project-name>/<scheme-name>/Assets.xcassets/AppIcon.appiconset
. This means that after downloading icon assets for a specific client’s build, you can change them on disk by simply deleting the existing AppIcon.appiconset
directory, and then copying the assets into the Assets.xcassets
directory.
name: Change iOS app icons
script: cp -r ./$CLIENT_ASSETS_FOLDER/ios_assets ios/Runner/Assets.xcassets/
Automatic build versioning
Each new app version that is published to Google Play or the Apple App Store needs to have a unique build number. You can use Codemagic’s CLI tools to retrieve the previous build number and then increment this for each new build. For example, the following shows how to increment the build number when building Flutter apps:
name: Flutter build aab with automatic versioning
script: |
flutter build appbundle --release \
--build-name=1.0.0 \
--build-number=$(($(google-play get-latest-build-number --package-name "$PACKAGE_NAME" --tracks="$GOOGLE_PLAY_TRACK") + 1))
name: Flutter build ipa with automatic versioning
script: |
flutter build ipa --release \
--build-name=1.0.0 \
--build-number=$(($(app-store-connect get-latest-testflight-build-number "$APP_STORE_ID") + 1)) \
--export-options-plist=/Users/builder/export_options.plist
Publishing to customer stores
You can automate the process of publishing each client’s app to their store account with Codemagic.
Make sure you add each client’s service account JSON key file to their environment variables group so Codemagic can authenticate and publish the app to their store.
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: <your-track>
If you’re using the automatic iOS code signing method, then each client’s environment group already has their App Store Connect account credentials, and they’ll be used to publish the app to this account.
publishing:
app_store_connect:
api_key: $APP_STORE_CONNECT_PRIVATE_KEY
key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
issuer_id: $APP_STORE_CONNECT_ISSUER_ID
Full YAML sample
Having followed all of the above steps, you now have a working codemagic.yaml
file that allows you to download client assets from AWS S3 bucket, chaning app name and icons, replacing exciting package name and bundle Id, build, code sign, automatically version and publish to each customer stores accounts.
Your final codemagic.yaml
file should look something like this:
workflows:
android-client-release:
name: Android client release
instance_type: mac_mini_m2
labels:
- ${CLIENT_ID} # Helpful when you open your Codemagic's builds page
environment:
groups:
- aws_credentials # Includes (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION)
vars:
S3_BUCKET_NAME: cmwhitelabel # The name of your S3 bucket that have all of your clients assets.
CLIENT_ASSETS_FOLDER: client_assets # The name of unzipped folder on the build machine that have the client assets.
ANDROID_ASSETS_FOLDER: android_assets # The name of your folder in S3 bucket that have the client's Android assets from (/android/app/src/main/res/).
IOS_ASSETS_FOLDER: ios_assets # The name of your folder in S3 bucket that have the client's iOS assets from (/ios/Runner/Assets.xcassets/).
GOOGLE_PLAY_TRACK: internal
scripts:
- name: Get assets from AWS S3 bucket
script: |
aws s3 cp s3://$S3_BUCKET_NAME/assets_${CLIENT_ID}.zip assets.zip
unzip assets.zip -d $CLIENT_ASSETS_FOLDER
- name: Set Package name
script: |
flutter pub add change_app_package_name
flutter pub run change_app_package_name:main $PACKAGE_NAME
- name: Change Android icons
script: cp -r ./$CLIENT_ASSETS_FOLDER/$ANDROID_ASSETS_FOLDER/* ./android/app/src/main/res
- name: Set up keystore
script: echo $CM_KEYSTORE | base64 --decode > $CM_KEYSTORE_PATH
- name: Install dependencies
script: flutter packages pub get
- name: Flutter build aab and automatic versioning
script: |
BUILD_NUMBER=$(($(google-play get-latest-build-number --package-name "$PACKAGE_NAME" --tracks="$GOOGLE_PLAY_TRACK") + 1))
flutter build appbundle --release \
--build-name=1.0.$BUILD_NUMBER \
--build-number=$BUILD_NUMBER
artifacts:
- build/**/outputs/**/*.aab
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: $GOOGLE_PLAY_TRACK
workflows:
ios-client-release:
name: iOS client release
instance_type: mac_mini_m2
labels:
- ${CLIENT_ID} # Helpful when you open your Codemagic's builds page
environment:
groups:
- aws_credentials # Includes (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION)
vars:
S3_BUCKET_NAME: cmwhitelabel # The name of your S3 bucket that have all of your clients assets.
CLIENT_ASSETS_FOLDER: client_assets # The name of unzipped folder on the build machine that have the client assets.
ANDROID_ASSETS_FOLDER: android_assets # The name of your folder in S3 bucket that have the client's Android assets from (/android/app/src/main/res/).
IOS_ASSETS_FOLDER: ios_assets # The name of your folder in S3 bucket that have the client's iOS assets from (/ios/Runner/Assets.xcassets/).
XCODE_WORKSPACE: "ios/Runner.xcworkspace"
XCODE_SCHEME: "Runner"
BASE_BUNDLE_ID: io.codemagic.whitelabel.dev # <-- Put the bundle ID that exists in the code, it will be replaced with the client's.
scripts:
- name: Get assets from AWS S3 bucket
script: |
aws s3 cp s3://$S3_BUCKET_NAME/assets_${CLIENT_ID}.zip assets.zip
unzip assets.zip -d $CLIENT_ASSETS_FOLDER
- name: Set bundle id # Replace the base bundle Id with the client's
script: |
PBXPROJ=$CM_BUILD_DIR/ios/Runner.xcodeproj/project.pbxproj
sed -i.bak "s/\$BASE_BUNDLE_ID/$BUNDLE_ID/g" $PBXPROJ
- name: Change iOS icons
script: cp -r ./$CLIENT_ASSETS_FOLDER/$IOS_ASSETS_FOLDER ios/Runner/Assets.xcassets/
- name: Install pods
script: find . -name "Podfile" -execdir pod install \;
- name: iOS code signing
script: |
keychain initialize
app-store-connect fetch-signing-files "$BUNDLE_ID" --type IOS_APP_STORE --create
keychain add-certificates
xcode-project use-profiles
- name: Install dependencies
script: flutter packages pub get
- name: Flutter build ipa and automatic versioning
script: |
BUILD_NUMBER=$(($(app-store-connect get-latest-app-store-build-number "$APP_STORE_ID") + 1))
flutter build ipa --release \
--build-name=1.0.$BUILD_NUMBER \
--build-number=$BUILD_NUMBER\
--export-options-plist=/Users/builder/export_options.plist
artifacts:
- build/ios/ipa/*.ipa
publishing:
app_store_connect:
api_key: $APP_STORE_CONNECT_PRIVATE_KEY
key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
issuer_id: $APP_STORE_CONNECT_ISSUER_ID
5. Start new builds via API
The Codemagic REST API is used in a white-label workflow to trigger builds for each unique client version you need to build. When triggering a build, you can pass environment variables in the API request’s payload that identify a specific client so their unique assets can be downloaded and used for the build, and the unique client environment group name that holds all the client secrets.
To trigger a build using the Codemagic REST API, you need your API access token, the application id, and the workflow id.
- The access token is available in the Codemagic UI under Teams > Personal Account > Integrations > Codemagic API > Show. You can then store this as an environment variable if you are calling the REST API from other workflows.
- Once you have added your app in Codemagic, open its settings and copy the application id from the browser address bar -
https://codemagic.io/app/<APP_ID>/settings
- The workflow id is the string value you assigned to the
name
property e.g “ios-qa-build”
An example of triggering a single build and passing an environment variable to specify the client id and a group to read the variables from might look like this:
curl -H "Content-Type: application/json" -H "x-auth-token: <auth-token>" \
--data '{
"appId": "<app-id>",
"workflowId": "<workflow-id>",
"branch": "<branch-name>",
"labels": ["<client-id>"],
"environment": {
"variables": {
"CLIENT_ID": "<client-id>"
},
"groups": [
"<client-group-name>"
]
}
}' \
https://api.codemagic.io/builds
Instead of triggering a single build for each client on your local machine manually, you can create a Codemagic workflow that loops through all of your client and trigger a new build for each one of them.
In the following example, you can trigger this workflow by merging a PR into this branch.
workflows:
trigger:
name: trigger builds for all clients
environment:
groups:
- cm_credentials # Includes (CM_API_KEY)
triggering:
events:
- pull_request
branch_patterns:
- pattern: '<current-branch-name>'
include: true
source: true
scripts:
- name: Trigger multiple client builds
script: |
CLIENTS=("001" "002" "003") # put your clients IDs here
for CLIENT in ${CLIENTS[@]}; do
echo "CLIENT: $CLIENT"
curl -H "Content-Type: application/json" -H "x-auth-token: ${CM_API_KEY}" \
--data '{
"appId": "<app-id>",
"workflowId": "<workflow-id>",
"branch": "<branch-name>",
"environment": {
"variables": {
"CLIENT_ID": "'${CLIENT}'"
},
"groups": [
"WL_${CLIENT}"
]
}
}' \
https://api.codemagic.io/builds
done
The Codemagic REST API can also be used for white label solutions where a dashboard is made available to your clients so they can customize an app themselves. This means they could upload their own icons, images, etc. to brand their app and then create a new build of their app. It could also be more advanced and allow clients to add their own distribution certificates, provisioning profiles and API keys.
You can find out more about the Codemagic REST API here
Check out the white label sample project here.