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

FieldRequiredDescription
nameYesA descriptive name shown in the dashboard.
instance_typeNoThe machine type: mac_mini_m4 (default) for iOS/macOS, or linux for Android.
max_build_durationNoMaximum build time in seconds. Default: 3600 (1 hour).
scriptsYesAn ordered list of script steps to execute. Each step has a name and script.
artifactsNoGlob patterns for files to collect after the build (e.g., "build/**/*.ipa").
triggeringNoBranch 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.plist

Examples 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:

  • workflows with name, instance_type and max_build_duration
  • scripts — each named step becomes a build step
  • environment.vars and environment.groups (variable groups are matched by name)
  • artifacts, including ** globs and absolute paths such as /tmp/xcodebuild_logs/*.log
  • publishing.app_store_connect and publishing.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:

VariableDescription
CM_BUILD_IDUnique build identifier.
CM_BRANCHGit branch being built.
CM_COMMITGit commit SHA.
CM_REPO_URLRepository clone URL.
CM_PROJECT_TYPEProject type (flutter, flutter_android, ios_native, android_native, react_native, react_native_android).
CM_PROJECT_PATHPath to the project root inside the build environment.
CM_APP_NAMEName of the application as configured in BuildMagic.
CM_PLATFORMTarget platform (ios or android).
CM_WORKFLOWName of the workflow being executed.
CM_GIT_PROVIDERgithub or gitlab.
CM_BUNDLE_IDiOS bundle identifier for the app.
CM_CERTIFICATE_URLURL to download the iOS distribution certificate (if configured).
CM_PROVISIONING_PROFILE_URLURL to download the iOS provisioning profile (if configured).
CM_KEYSTORE_PATHPath to the Android keystore on the build machine, already in place before the first step runs.
CM_KEYSTORE_PASSWORD, CM_KEY_ALIAS, CM_KEY_PASSWORDAndroid signing credentials.
CM_ASC_KEY_URL, CM_ASC_KEY_ID, CM_ASC_ISSUER_IDApp 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 --release

Local Config Override

You can edit the buildmagic.yaml directly in the BuildMagic dashboard without modifying your repository:

  1. Go to your app's Settings page.
  2. Click the buildmagic.yaml tab.
  3. The config from your repo is loaded automatically. Edit as needed.
  4. Click Save changes to create a local override.
  5. Builds will use your local copy instead of the repo file.
  6. 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:

  1. Fetch sources — Clone the repository and detect the project root, including projects nested inside a monorepo.
  2. 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).
  3. Install dependencies — CocoaPods, Gradle or pub, as the project requires. Expo projects get expo prebuild automatically.
  4. Set the build number — For iOS, resolved from App Store Connect.
  5. Set up code signing — Install the certificate and profile, creating them through the App Store Connect API if automatic management is on.
  6. Build — Produce the IPA, APK or App Bundle.
  7. Sign artifacts — For Android, sign and zipalign, then verify. A signing failure fails the build.
  8. Collect artifacts — Gather the outputs. This runs even when an earlier step failed, so a partial build still leaves something to inspect.
  9. 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:

  1. buildmagic.yaml
  2. buildmagic.yml
  3. .buildmagic.yaml
  4. .buildmagic.yml
  5. codemagic.yaml
  6. codemagic.yml
  7. .codemagic.yaml
  8. .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

StatusMeaning
QueuedWaiting for an available build worker.
PreparingSetting up the build environment (VM or container).
BuildingExecuting pipeline steps.
CompletedAll steps finished successfully.
FailedA step encountered an error. Check build logs for details.
CancelledThe build was cancelled by a user.