Workflows & buildmagic.yaml
BuildMagic uses a buildmagic.yaml configuration file to define your build pipeline. Place this file in the root of your repository to fully control how your app is built, tested, and published.
The buildmagic.yaml File
Create a file named buildmagic.yaml in the root of your repository. This file defines one or more workflows, each containing a series of script steps that run sequentially during a build.
Basic Structure
workflows:
my-workflow:
name: Build & Test
instance_type: mac_mini_m4
max_build_duration: 3600
scripts:
- name: Install dependencies
script: |
# Your commands here
npm install
- name: Run tests
script: |
npm test
- name: Build
script: |
npm run build
artifacts:
- "build/**/*.ipa"
- "build/**/*.apk"Workflow Fields
| Field | Required | Description |
|---|---|---|
name | Yes | A descriptive name shown in the dashboard. |
instance_type | No | The machine type: mac_mini_m4 (default) for iOS/macOS, or linux for Android. |
max_build_duration | No | Maximum build time in seconds. Default: 3600 (1 hour). |
scripts | Yes | An ordered list of script steps to execute. Each step has a name and script. |
artifacts | No | Glob patterns for files to collect after the build (e.g., "build/**/*.ipa"). |
triggering | No | Branch patterns to determine which workflow runs for which branch. |
Script Steps
Each script step runs as a shell command in the build environment. Steps execute sequentially — if any step fails (non-zero exit code), the build stops and is marked as failed.
scripts:
- name: Install dependencies
script: |
cd $CM_PROJECT_PATH
pod install --repo-update
- name: Build archive
script: |
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-archivePath /tmp/build/App.xcarchive \
-configuration Release
- name: Export IPA
script: |
xcodebuild -exportArchive \
-archivePath /tmp/build/App.xcarchive \
-exportPath /tmp/artifacts \
-exportOptionsPlist ExportOptions.plistExamples by Project Type
iOS Native (Swift/ObjC with CocoaPods)
workflows:
ios-build:
name: iOS Build
instance_type: mac_mini_m4
scripts:
- name: Install CocoaPods
script: |
cd $CM_PROJECT_PATH
pod install --repo-update
- name: Build archive
script: |
cd $CM_PROJECT_PATH
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-archivePath /tmp/build/App.xcarchive \
-configuration Release \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
- name: Export IPA
script: |
xcodebuild -exportArchive \
-archivePath /tmp/build/App.xcarchive \
-exportPath /tmp/artifacts \
-exportOptionsPlist ExportOptions.plist
artifacts:
- "/tmp/artifacts/*.ipa"iOS Native (Swift Package Manager)
workflows:
ios-spm:
name: iOS Build (SPM)
instance_type: mac_mini_m4
scripts:
- name: Resolve packages
script: |
cd $CM_PROJECT_PATH
xcodebuild -resolvePackageDependencies \
-project MyApp.xcodeproj \
-scheme MyApp
- name: Build archive
script: |
cd $CM_PROJECT_PATH
xcodebuild archive \
-project MyApp.xcodeproj \
-scheme MyApp \
-archivePath /tmp/build/App.xcarchive \
-configuration Release
artifacts:
- "/tmp/build/**/*.xcarchive"Flutter iOS
workflows:
flutter-ios:
name: Flutter iOS
instance_type: mac_mini_m4
scripts:
- name: Install dependencies
script: |
cd $CM_PROJECT_PATH
flutter pub get
cd ios && pod install --repo-update
- name: Run tests
script: |
cd $CM_PROJECT_PATH
flutter test
- name: Build IPA
script: |
cd $CM_PROJECT_PATH
flutter build ipa --release
artifacts:
- "build/ios/ipa/*.ipa"Android Native (Gradle)
workflows:
android-build:
name: Android Build
instance_type: linux
scripts:
- name: Build APK
script: |
cd $CM_PROJECT_PATH
./gradlew assembleRelease
- name: Build AAB
script: |
cd $CM_PROJECT_PATH
./gradlew bundleRelease
artifacts:
- "app/build/outputs/**/*.apk"
- "app/build/outputs/**/*.aab"React Native iOS
workflows:
rn-ios:
name: React Native iOS
instance_type: mac_mini_m4
scripts:
- name: Install JS dependencies
script: |
cd $CM_PROJECT_PATH
npm ci
- name: Install CocoaPods
script: |
cd $CM_PROJECT_PATH/ios
pod install --repo-update
- name: Build
script: |
cd $CM_PROJECT_PATH/ios
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-archivePath /tmp/build/App.xcarchive \
-configuration Release
artifacts:
- "/tmp/artifacts/*.ipa"React Native Android
workflows:
rn-android:
name: React Native Android
instance_type: linux
scripts:
- name: Install JS dependencies
script: |
cd $CM_PROJECT_PATH
npm ci
- name: Build APK
script: |
cd $CM_PROJECT_PATH/android
./gradlew assembleRelease --no-daemon
- name: Build AAB
script: |
cd $CM_PROJECT_PATH/android
./gradlew bundleRelease --no-daemon
artifacts:
- "android/app/build/outputs/**/*.apk"
- "android/app/build/outputs/**/*.aab"Automatic build numbers
App Store Connect rejects an upload whose CFBundleVersion was already used, so a build number kept by hand goes stale as soon as anything uploads. For iOS builds, BuildMagic asks App Store Connect for the highest build number your app has ever uploaded and uses the next one — across all version trains, so it stays correct after a version bump.
It needs an App Store Connect API key in your team's code signing settings and a bundle ID on the app. Without either, or if the app is not in App Store Connect yet, the build keeps whatever number the project already carries and says so in the log — it never fails the build. Set CM_AUTO_BUILD_NUMBER=false as a variable to turn it off and manage the number yourself.
Expo projects
Expo apps keep ios/ and android/ out of version control and regenerate them from app.json. The React Native pipelines detect an Expo project and run expo prebuild automatically before the native build, so nothing extra is needed. If the native directories are committed, the step is skipped and they are used as-is.
For Android, google-services.json is usually gitignored but required by prebuild. Add a variable named GOOGLE_SERVICES_JSON holding the base64 (or the raw JSON) of the file, and the build writes it into the project before prebuild runs.
Migrating from Codemagic
A step-by-step walkthrough lives in Migrating from Codemagic. In short:
BuildMagic reads codemagic.yaml directly — you do not have to rename it. The schema is a superset of Codemagic's, so an existing file is picked up as-is, and buildmagic.yaml wins if both are present.
What carries over unchanged:
workflowswithname,instance_typeandmax_build_durationscripts— each named step becomes a build stepenvironment.varsandenvironment.groups(variable groups are matched by name)artifacts, including**globs and absolute paths such as/tmp/xcodebuild_logs/*.logpublishing.app_store_connectandpublishing.google_play
When a file declares one workflow per platform, the instance_typedecides which one runs: a mac_* instance is selected for iOS builds and a linux_* instance for Android builds.
Scripts start at the repository root, the same as in Codemagic, so a step that sayscd apps/mobile resolves against the clone directory and not against the detected project. CM_PROJECT_PATH defaults to ., so cd $CM_PROJECT_PATH is harmless when the project sits at the root.
The Android keystore is on the machine before the first step runs, at CM_KEYSTORE_PATH, so the usual storeFile=$CM_KEYSTORE_PATH in a generated key.properties works unchanged.
Variables your file reads but that are not defined in BuildMagic are reported before the build starts, so a missing GOOGLE_SERVICES_JSON is named up front instead of surfacing later as an unrelated-looking Gradle error.
Not yet supported: the Codemagic CLI tools (keychain,xcode-project, app-store-connect). Scripts calling them need rewriting, or you can drop those steps and let BuildMagic handle signing and publishing through its own code signing settings.
Version pins for tools the image cannot switch — xcode, cocoapods, node, java, ruby — are read but not applied. The build reports the difference in its first lines, so a failure caused by the version gap is recognisable instead of surfacing as an unrelated-looking compiler error:
Note: this configuration asks for Xcode 26.3 and this machine has 26.4.1.
BuildMagic does not switch Xcode versions, so the build uses 26.4.1.
If the build fails inside a dependency, this difference is worth checking first.flutter is honoured: the version you pin is installed through FVM for that build.
Environment Variables
The following environment variables are available in all script steps:
| Variable | Description |
|---|---|
CM_BUILD_ID | Unique build identifier. |
CM_BRANCH | Git branch being built. |
CM_COMMIT | Git commit SHA. |
CM_REPO_URL | Repository clone URL. |
CM_PROJECT_TYPE | Project type (flutter, flutter_android, ios_native, android_native, react_native, react_native_android). |
CM_PROJECT_PATH | Path to the project root inside the build environment. |
CM_APP_NAME | Name of the application as configured in BuildMagic. |
CM_PLATFORM | Target platform (ios or android). |
CM_WORKFLOW | Name of the workflow being executed. |
CM_GIT_PROVIDER | github or gitlab. |
CM_BUNDLE_ID | iOS bundle identifier for the app. |
CM_CERTIFICATE_URL | URL to download the iOS distribution certificate (if configured). |
CM_PROVISIONING_PROFILE_URL | URL to download the iOS provisioning profile (if configured). |
CM_KEYSTORE_PATH | Path to the Android keystore on the build machine, already in place before the first step runs. |
CM_KEYSTORE_PASSWORD, CM_KEY_ALIAS, CM_KEY_PASSWORD | Android signing credentials. |
CM_ASC_KEY_URL, CM_ASC_KEY_ID, CM_ASC_ISSUER_ID | App Store Connect API key, for publishing and build numbers. |
The full list, including the Android publishing variables and the ones you can set to change behaviour, is in Environment Variables.
Branch Triggering
When you have multiple workflows, use branch patterns to control which workflow runs for each branch:
workflows:
develop:
name: Dev Build
triggering:
branch_patterns:
- pattern: develop
include: true
scripts:
- name: Build debug
script: flutter build apk --debug
release:
name: Release Build
triggering:
branch_patterns:
- pattern: main
include: true
- pattern: "release/*"
include: true
scripts:
- name: Build release
script: flutter build ipa --releaseLocal Config Override
You can edit the buildmagic.yaml directly in the BuildMagic dashboard without modifying your repository:
- Go to your app's Settings page.
- Click the buildmagic.yaml tab.
- The config from your repo is loaded automatically. Edit as needed.
- Click Save changes to create a local override.
- Builds will use your local copy instead of the repo file.
- Click Discard override to revert to the repo version.
This is useful for quick fixes — e.g., fixing a typo in a build command without pushing a commit.
Default Pipeline (No buildmagic.yaml)
If your repository does not contain a buildmagic.yaml file, BuildMagic will generate a default pipeline based on your project type. The default pipeline includes:
- Fetch sources — Clone the repository and detect the project root, including projects nested inside a monorepo.
- Set up the SDK — Select the Flutter version, or install JavaScript dependencies with the package manager the repository actually uses (npm, Yarn or pnpm, workspace-aware).
- Install dependencies — CocoaPods, Gradle or pub, as the project requires. Expo projects get
expo prebuildautomatically. - Set the build number — For iOS, resolved from App Store Connect.
- Set up code signing — Install the certificate and profile, creating them through the App Store Connect API if automatic management is on.
- Build — Produce the IPA, APK or App Bundle.
- Sign artifacts — For Android, sign and zipalign, then verify. A signing failure fails the build.
- Collect artifacts — Gather the outputs. This runs even when an earlier step failed, so a partial build still leaves something to inspect.
- Publish — Upload to TestFlight or Google Play when configured.
The default pipeline handles most projects without a configuration file. Add one when you need steps of your own — tests, code generation, a custom export — or when you are bringing a pipeline over from Codemagic.
Supported File Names
BuildMagic looks for the configuration file in the following order, and uses the first one it finds:
buildmagic.yamlbuildmagic.yml.buildmagic.yaml.buildmagic.ymlcodemagic.yamlcodemagic.yml.codemagic.yaml.codemagic.yml
A codemagic.yaml is read as-is, so a repository migrating from Codemagic needs no renaming. A buildmagic.yaml wins when both exist, which lets you keep the original file untouched while trying something different.
Triggering Builds
You can trigger builds in several ways:
- Dashboard — Click "Start Build" on any app. Select the branch and click Start.
- Git Webhooks — Set up a webhook on GitHub or GitLab to trigger builds on push events. See Integrations.
- API — Use the REST API to trigger builds programmatically.
Build Statuses
| Status | Meaning |
|---|---|
| Queued | Waiting for an available build worker. |
| Preparing | Setting up the build environment (VM or container). |
| Building | Executing pipeline steps. |
| Completed | All steps finished successfully. |
| Failed | A step encountered an error. Check build logs for details. |
| Cancelled | The build was cancelled by a user. |