Skip to content

YAML Reference

The RunnerHub pipeline configuration is defined in a single YAML file located at .runnerhub/runnerhub.yml in your repository root, or stored as branch-specific Cloud YAML in the dashboard.

By default, RunnerHub looks for the configuration file at:

repo-root/
├─ .runnerhub/
│ └─ runnerhub.yml ← primary (checked first)
└─ runnerhub.yml ← fallback (if primary not found)

If multiple apps share the same repository (e.g., white-label builds), each app can specify a custom pipeline YAML path in App Settings → Pipeline Configuration Path.

When a custom path is set:

  • RunnerHub fetches only that file (no fallback)
  • The path is relative to the repository root
  • Must end in .yml or .yaml
  • Path traversal (..) is not allowed

Example setup for white-label apps:

repo-root/
├─ .runnerhub/
│ ├─ client-a.yml ← App "Client A" uses this
│ ├─ client-b.yml ← App "Client B" uses this
│ └─ runnerhub.yml ← Default (apps without custom path)

The smallest valid pipeline configuration requires four fields:

name: My First Pipeline
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Build
run: echo "Building..."

Here is the full structure of a RunnerHub pipeline configuration:

name: string
platform: ios | macos | android | flutter | react-native
target_platform: ios | macos | android | web # (Optional, only when platform: flutter)
environment:
xcode: "16.4" # (Required for iOS/macOS/React Native iOS targets)
android_sdk: 34 # (Required for Android/React Native Android targets)
flutter: "3.24.0" # (Required for Flutter, see note below)
node: lts # (Required for React Native)
ruby: "3.2" # (Optional, for Bundler/CocoaPods)
variables:
KEY: value
triggers:
- push
- pull_request
# Optional: use object format for branch filtering
- event: push
branches:
- main
- release/*
steps:
- name: string
run: string
working_directory: string
env:
KEY: value
artifacts:
- glob/path/**
timeout: number

| Field | Required | Type | Description | |-------|----------|------|-------------| | name | Yes | string | Human-readable pipeline name, displayed in dashboard and logs | | platform | Yes | string | Target platform: ios, macos, android, flutter, or react-native | | target_platform | No | string | Flutter only. Which native target this pipeline (or job) produces: ios, macos, android, or web. Can also be set per job, where it overrides this value. Omit it to keep today's behavior. See Flutter Native Targets | | environment | Yes | object | Platform-specific tool versions (see Platform Requirements below) | | triggers | Yes | array | List of events that trigger the pipeline; use string format (push, pull_request) or object format with optional branch filtering (see Triggers) | | steps | Yes | array | Ordered list of commands to execute | | artifacts | No | array | Glob patterns for build outputs to collect and store | | timeout | No | number | Maximum job duration in minutes. Defaults to the plan maximum (Free 30 / PAYG 60 / Pro 90 / Business 120). If omitted, the plan maximum is used. |

Triggers support two formats:

String format — trigger on all branches:

triggers:
- push
- pull_request

Object format — trigger with optional branch filtering:

triggers:
- event: push
branches:
- main
- develop
- release/*
- event: pull_request
branches:
- main

The branches field is optional and only works with push and pull_request events. Manual runs from the dashboard always work regardless of your triggers: array. Scheduled builds are configured in the dashboard (Settings → Scheduled Triggers) and do not require a YAML trigger entry.

For detailed information about branch patterns (wildcards, limits, and PR behavior), see the Triggers guide.

Each step in the steps array supports these fields:

| Field | Required | Type | Description | |-------|----------|------|-------------| | name | Yes | string | Human-readable step name, displayed in logs and dashboard | | run | Yes | string | Shell command(s) to execute | | working_directory | No | string | Directory where the command runs (useful for monorepos) | | env | No | object | Step-level environment variables (override pipeline-level variables) | | if | No | string | Conditional execution based on status functions and context (see Conditional Execution) | | retry | No | number | Number of retries on failure (max 10) | | retry_delay | No | number | Seconds to wait between retry attempts (max 300) |

Here's a complete iOS pipeline demonstrating all major features:

name: iOS Release Build
platform: ios
environment:
xcode: "16.4"
variables:
LC_ALL: en_US.UTF-8
FASTLANE_USER: $FASTLANE_USER
FASTLANE_PASSWORD: $FASTLANE_PASSWORD
triggers:
- push
- pull_request
steps:
- name: Install dependencies
run: bundle install
- name: Install pods
run: pod install
- name: Run tests
run: fastlane test
- name: Build for release
run: fastlane build
env:
BUILD_CONFIG: Release
- name: Upload to TestFlight
run: fastlane beta
working_directory: ios
env:
TESTFLIGHT_ENABLED: "true"
artifacts:
- build/**/*.ipa
- build/**/*.dSYM
timeout: 45

Each platform requires specific tool version specifications in the environment block. The table below summarises which subfield is required for each platform — the backend validates this on every pipeline run:

| Platform | Required environment subfield | Example value | |----------|---------------------------------|---------------| | ios / macos | xcode | "16.4" | | android | android_sdk | 34 | | flutter | flutter | "3.24.0" | | react-native | node | lts or "20.11" |

Optional subfields (ruby, secondary platform tools) are allowed alongside the required one but are never sufficient on their own.

platform names the framework or OS family your app belongs to, and it must match the platform you chose when you created the app in RunnerHub. A Flutter app therefore always declares platform: flutter — it can never declare platform: macos.

That leaves no way to say "this particular job produces a Mac app", which matters because macOS builds need a different provisioning profile type, a Mac Installer certificate, and a .pkg (not an .ipa) to deploy. target_platform fills that gap.

| Value | Produces | |-------|----------| | ios | iOS app / .ipa | | macos | Mac app / .app or .pkg | | android | APK or App Bundle | | web | Web bundle |

Rules:

  • Optional. If you omit it, nothing changes — your pipeline behaves exactly as it does today.
  • Flutter only. Declaring it on an ios, macos, android, or react-native pipeline is a validation error at save time. React Native is not supported in this release.
  • Two placements. Top level (single-job pipelines) and inside an individual job (multi-job pipelines).
  • Per job wins. A job's own target_platform overrides the top-level value. Matrix-expanded jobs inherit the value from the job they were expanded from.

Single-job:

name: Flutter macOS Build
platform: flutter
target_platform: macos
environment:
flutter: "3.24.0"
triggers:
- push
steps:
- name: Install dependencies
run: flutter pub get
- name: Build macOS
run: flutter build macos --release
artifacts:
- build/macos/Build/Products/Release/*.app

Multi-job — one Flutter app, three native targets:

name: Flutter All Targets
platform: flutter
environment:
flutter: "3.24.0"
triggers:
- push
jobs:
ios_build:
name: Build iOS
target_platform: ios
steps:
- name: Install dependencies
run: flutter pub get
- name: Build IPA
run: flutter build ipa --release
artifacts:
- build/ios/ipa/*.ipa
macos_build:
name: Build macOS
target_platform: macos
steps:
- name: Install dependencies
run: flutter pub get
- name: Build macOS
run: flutter build macos --release
artifacts:
- build/macos/Build/Products/Release/*.app
android_build:
name: Build Android
target_platform: android
steps:
- name: Install dependencies
run: flutter pub get
- name: Build APK
run: flutter build apk --release
artifacts:
- build/app/outputs/**/*.apk

The resolved value is also exposed to your steps as the RUNNERHUB_TARGET_PLATFORM system variable — an empty string when the field is not declared.

For complete macOS build, signing, and deploy recipes, see the Flutter cookbook.

environment:
xcode: "16.4" # Required: specify Xcode version
variables:
KEY: value

Use the xcode field to specify your Xcode version. Available versions depend on the agent image.

environment:
android_sdk: 34 # Required: specify Android SDK level
variables:
KEY: value
environment:
node: lts # Required: Node.js version ("lts", "latest", or semver like "20.11")
xcode: "16.4" # Optional: for iOS targets
android_sdk: 34 # Optional: for Android targets
ruby: "3.2" # Optional: advisory only (see note below)
variables:
KEY: value

Required: You must specify node and at least one of xcode (iOS) or android_sdk (Android).

Accepted values for node:

  • Semantic version: "20", "20.11", "20.11.1" — Install specific version
  • Release channels: "lts" — Install current LTS version; "latest" — Install latest version

The specified Node version is activated before any steps run.

About ruby: The ruby: field is currently advisory only — the platform does not enforce a Ruby version based on this setting. To pin a Ruby version, commit a .ruby-version file to your repository root. rbenv (pre-installed on the VM) will automatically detect and activate the specified Ruby version before your pipeline runs.


For complex pipelines with multiple stages, you can define multiple jobs that run in sequence or parallel, with dependency management. This section covers the multi-job (DAG) format where you use jobs: instead of steps: at the top level.

Single-job format — uses steps: at the top level:

name: My Pipeline
platform: ios
triggers:
- push
steps:
- name: Build
run: xcodebuild build
- name: Test
run: xcodebuild test

Multi-job format — uses jobs: at the top level; each job has its own steps::

name: My Pipeline
platform: ios
triggers:
- push
jobs:
build:
name: Build App
steps:
- name: Build
run: xcodebuild build
test:
name: Run Tests
steps:
- name: Test
run: xcodebuild test

When using jobs:, the top-level steps: field is ignored.

Each key under jobs: is a unique identifier for that job:

jobs:
build: # Job key (identifier)
name: Build App # Display name
steps: [...]
test-unit: # Job key
name: Unit Tests # Display name
steps: [...]
  • Job key (e.g., build, test-unit) — used in needs: dependencies and logs
  • name: — human-readable display name shown in the dashboard

The needs: field specifies which jobs must complete successfully before this job runs:

jobs:
build:
name: Build App
steps:
- name: Build
run: xcodebuild build
test:
name: Run Tests
needs: [build] # Test waits for build to succeed
steps:
- name: Test
run: xcodebuild test
deploy:
name: Deploy
needs: [test] # Deploy waits for test to succeed
steps:
- name: Deploy
run: fastlane beta

Dependency behavior:

  • If a dependency fails, the dependent job is automatically cancelled
  • A job waits for all items in needs: to complete successfully before starting
  • Jobs without dependencies start immediately

Jobs without mutual needs: dependencies run concurrently:

jobs:
build:
name: Build
steps:
- name: Build
run: xcodebuild build
test-unit:
name: Unit Tests
needs: [build] # Waits for build
steps:
- name: Run unit tests
run: xcodebuild test
test-ui:
name: UI Tests
needs: [build] # Also waits for build
steps:
- name: Run UI tests
run: xcodebuild test -scheme UITests

In this example:

  1. build runs first
  2. test-unit and test-ui run in parallel after build completes
  3. This is faster than running them sequentially
  • Pipeline SUCCESS: All jobs succeed
  • Pipeline FAILED: Any job fails
  • Cascading failures: When a job fails, all dependent jobs are automatically marked FAILED with reason Dependency condition not met: <condition>

Example:

jobs:
setup:
steps: [...]
build:
needs: [setup]
steps: [...]
test:
needs: [build]
steps: [...]
deploy:
needs: [test]
steps: [...]

If build fails:

  • test and deploy are automatically marked FAILED with reason Dependency condition not met: needs build
  • Pipeline status becomes FAILED

Complete Example: Build → Parallel Tests → Archive

Section titled “Complete Example: Build → Parallel Tests → Archive”

Here's a realistic iOS pipeline with parallel test stages. Note: Each job installs its own dependencies (cache makes this fast).

name: CI Pipeline
platform: ios
environment:
xcode: "16.4"
triggers:
- push
- event: pull_request
branches: [main, develop]
jobs:
build:
name: Build App
steps:
- name: Install dependencies
run: bundle install
- name: Install pods
run: pod install
- name: Build
run: xcodebuild build -scheme MyApp -destination 'generic/platform=iOS'
test-unit:
name: Unit Tests
needs: [build]
steps:
- name: Install dependencies
run: bundle install && pod install
- name: Run unit tests
run: xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 16'
test-ui:
name: UI Tests
needs: [build]
steps:
- name: Install dependencies
run: bundle install && pod install
- name: Run UI tests
run: xcodebuild test -scheme MyAppUITests -destination 'platform=iOS Simulator,name=iPhone 16'
archive:
name: Archive
needs: [test-unit, test-ui]
steps:
- name: Install dependencies
run: bundle install && pod install
- name: Archive
run: xcodebuild archive -scheme MyApp -archivePath build/MyApp.xcarchive
deploy:
name: Deploy to TestFlight
needs: [archive]
if: success()
steps:
- name: Upload to TestFlight
if: branch == 'main'
run: fastlane beta

The object form requires explicit event: and branches: keys; pull_request: branches: [...] is not accepted.

Execution flow:

  1. build runs
  2. test-unit and test-ui run in parallel after build completes (each installs its own deps)
  3. archive waits for both tests to complete and installs its own deps
  4. deploy runs only after archive succeeds; the Upload to TestFlight step only runs on the main branch

Note: Job-level if: only accepts status functions. To gate an entire job by branch, use trigger-level branches: filtering instead. A job with all steps skipped still finishes with SUCCESS status.

Each job in jobs: supports:

jobs:
job-id:
name: Display name # Required: human-readable name
needs: [other-job-id, ...] # Optional: job dependencies
if: condition # Optional: conditional execution
target_platform: macos # Optional: Flutter only; overrides the
# pipeline-level value for this job
steps: # Required: list of steps for this job
- name: Step name
run: command
working_directory: path
env:
KEY: value
retry: 2
retry_delay: 5

All step properties (retry, env, working_directory, etc.) work the same way as single-job pipelines.


The if: field on a job can only use status functions — no branch or event conditions:

jobs:
build:
name: Build
steps:
- run: xcodebuild build
notify:
name: Notify
needs: [build]
if: success() # Only status functions allowed here
steps:
- run: curl https://example.com/notify

Supported job-level if: values:

  • success() — job runs if all dependencies succeeded
  • failure() — job runs if any dependency failed
  • always() — job always runs
  • cancelled() — job runs if any dependency was cancelled

Branch and event conditions (e.g., branch == 'main', event == 'pull_request') are not allowed at the job level. Use them at the step level instead, or gate job execution via triggers: branch filtering.

Execute steps only when specific conditions are met:

steps:
- name: Build
run: xcodebuild build
- name: Notify on Success
if: success()
run: curl https://example.com/notify?status=success
- name: Notify on Failure
if: failure()
run: curl https://example.com/notify?status=failure
- name: Deploy (main branch only)
if: success() && branch == 'main'
run: fastlane beta
- name: Cleanup (always)
if: always()
run: rm -rf build

Supported step-level if: grammar:

| Expression | Description | |------------|-------------| | success() | True if all previous steps succeeded | | failure() | True if any previous step failed | | always() | Always true (step always runs) | | cancelled() | True if the job was cancelled | | branch == 'main' | True if the current branch matches exactly | | branch != 'main' | True if the current branch does not match | | branch =~ /^(main\|develop)$/ | True if the branch matches the regex pattern (branch only) | | event == 'pull_request' | True if triggered by a pull request | | event != 'push' | True if not a push trigger |

String literals must be wrapped in single quotes ('main'); double-quoted literals are not accepted.

Combining conditions: Use && to combine expressions — it is the only supported logical operator:

if: success() && branch == 'main'
if: failure() && event == 'push'

Regex operator (=~): Supported for branch onlyevent accepts only == and !=. The pattern must be a JavaScript-compatible regex delimited by /, at most 256 characters, and must compile:

if: branch =~ /^release\//

Context variables (step-level only):

  • branch — Current git branch name (e.g., main, develop)
  • event — Trigger type (e.g., push, pull_request, manual, schedule). Note: The YAML triggers: array only accepts push and pull_request; manual and schedule are runtime trigger types available when builds are triggered from the dashboard or schedules.

To condition on environment variables, use shell-level if [ "$VAR" = "..." ] inside run: instead of if: expressions.


Automatically retry a step if it fails:

steps:
- name: Build
run: xcodebuild build
retry: 2
retry_delay: 5 # seconds between attempts
- name: Upload Artifact
run: curl -X POST -T app.ipa https://webhook.example.com/upload
retry: 3
retry_delay: 10

Behavior:

  • Step runs up to (retry + 1) total attempts before failing
  • If retry_delay is specified, wait that many seconds between attempts
  • All attempts are logged
  • Final failure fails the entire job
  • retry is silently capped at 10; retry_delay is silently capped at 300 seconds

Run multiple steps simultaneously within a single job. All parallel steps share the same filesystem, environment variables, and working directory.

Parallel steps execute concurrently on the same VM, unlike multi-job pipelines where each job gets its own isolated VM. This means:

  • All steps can read and write to the same files
  • Environment variables and working directory changes persist across parallel steps
  • There is no filesystem isolation between parallel steps

This is ideal for running independent operations (tests, linting, builds) that don't depend on each other's output.

steps:
- name: Setup
run: pod install
- parallel:
- name: Run Unit Tests
run: xcodebuild test -scheme UnitTests
- name: Run UI Tests
run: xcodebuild test -scheme UITests
- name: Lint Code
run: swiftlint
- name: Build For Release
run: xcodebuild build -scheme MyApp -configuration Release

Important: The parallel: key contains an array of steps (note the proper indentation with nested dashes).

All standard step features work inside parallel: blocks:

- parallel:
- name: Test with Retry
run: xcodebuild test
retry: 2
retry_delay: 5
- name: Conditional Lint
if: branch == 'main'
run: swiftlint
- name: Build in Subdirectory
run: xcodebuild build
working_directory: ios
- name: Test with Custom Env
run: xcodebuild test
env:
ENABLE_TESTING: "true"
SWIFT_VERSION: "5.10"
  • if: — conditionally include a parallel step
  • retry: — automatically retry if the step fails
  • retry_delay: — seconds to wait between retries
  • env: — step-level environment variables
  • working_directory: — run in a specific directory

Execution:

  • All steps in a parallel: group start at the same time
  • The pipeline waits for ALL parallel steps to finish before moving to the next sequential step
  • Order of execution within the parallel group is not guaranteed

Failure Handling:

  • If one parallel step fails, the other parallel steps continue running to completion (they don't cancel each other)
  • After a parallel group, if any step failed, subsequent sequential steps are skipped (the job fails)
  • Use if: always() on a sequential step to run it regardless of previous failures

Example with failure handling:

steps:
- name: Setup
run: pod install
- parallel:
- name: Unit Tests
run: xcodebuild test -scheme UnitTests
- name: UI Tests
run: xcodebuild test -scheme UITests
- name: Generate Report
if: always() # Runs even if tests failed
run: xcrun xcodebuild -resultBundlePath build/test-results
- name: Deploy
if: success() # Only if everything succeeded
run: fastlane beta

By default, when one step in a parallel: group fails, the remaining siblings continue running to completion. You can override this with fail_fast: true:

- parallel:
fail_fast: true # Cancel siblings on first failure
steps:
- name: Unit Tests
run: xcodebuild test -scheme UnitTests
- name: Integration Tests
run: xcodebuild test -scheme IntegrationTests
- name: UI Tests
run: xcodebuild test -scheme UITests

When fail_fast: true:

  • The first parallel step to exit with a non-zero code causes the agent to cancel the SSH sessions for all still-running siblings immediately.
  • This is useful for saving metered macOS minutes — no reason to wait for a 20-minute UI test suite if the unit tests already failed.
  • Caveat: cancellation terminates the SSH session promptly; a remote process that ignores SIGHUP may linger momentarily. This is harmless because each job runs in an ephemeral VM that is torn down completely once the job ends, regardless of any lingering processes.

Default (fail_fast: false or omitted): All parallel steps run to completion before the group outcome is evaluated. Use this when you want full diagnostic output from all suites even when one fails.

1. Test Suite in Parallel

steps:
- name: Install dependencies
run: pod install
- parallel:
- name: Unit Tests
run: xcodebuild test -scheme MyAppTests
- name: Integration Tests
run: xcodebuild test -scheme IntegrationTests
- name: Snapshot Tests
run: xcodebuild test -scheme SnapshotTests
- name: Build for Distribution
run: xcodebuild build -scheme MyApp -configuration Release

2. Build Multiple Targets

- parallel:
- name: Build Main App
run: xcodebuild build -scheme MyApp
- name: Build Watch App
run: xcodebuild build -scheme MyAppWatch
- name: Build Share Extension
run: xcodebuild build -scheme MyAppShare

3. Lint and Test in Parallel

- parallel:
- name: Run Tests
run: fastlane test
- name: Lint Code
run: swiftlint
- name: Check Syntax
run: swift build

Parallel steps are not suitable when:

  • Steps depend on each other's output — e.g., build produces artifacts that tests need. Use sequential steps instead
  • Steps modify the same files — concurrent writes can conflict. Sequence them or use different file paths
  • You need VM isolation — use multi-job pipelines (jobs:) instead of parallel steps

Create multiple job variants from a single job definition:

jobs:
test:
name: Test ${{ matrix.swift_version }}
strategy:
matrix:
swift_version: ["5.8", "5.9", "5.10"]
os: [ios, macos]
steps:
- name: Setup Swift ${{ matrix.swift_version }}
run: swift --version
- name: Test on ${{ matrix.os }}
run: swift test

This creates 6 jobs (3 Swift versions × 2 OSes).

Matrix features:

  • ${{ matrix.KEY }} substitutes values in step name, run, env values, working_directory, and if expressions — any field where the placeholder appears is expanded
  • Environment variables like MATRIX_SWIFT_VERSION, MATRIX_OS are also injected and available as shell $MATRIX_* variables (useful if you need the value in a multi-line run: script)
  • Each matrix combination becomes an independent job
  • Matrix variants are scheduled independently — one variant failing does not cancel its sibling variants. However, any job that needs: a matrix job waits for all variants of that job to succeed

Optional fields:

strategy:
matrix:
node: [14, 16, 18]
exclude:
- node: 14
max-parallel: 2 # Limit concurrent jobs from this matrix
  • exclude: — Exclude specific combinations
  • max-parallel: — Max simultaneous jobs (1-50)

RunnerHub supports limited expression interpolation in pipeline YAML. Only the ${{ matrix.* }} namespace is expanded by the platform, and it is expanded in step name, run, env values, working_directory, and if expressions:

jobs:
test:
name: Test ${{ matrix.swift_version }}
strategy:
matrix:
swift_version: ["5.8", "5.9", "5.10"]
steps:
- run: swift ${{ matrix.swift_version }} --version

Important: The following interpolations are NOT supported and will not be expanded:

  • ${{ env.VARIABLE }} — Use shell $VARIABLE instead
  • ${{ secrets.SECRET }} — Use shell $SECRET instead (secrets are injected as env vars)
  • ${{ vars.VAR }} — Use shell $VAR instead (variables are injected as env vars)
  • ${{ github.* }} — GitHub context is not available

Instead, reference environment variables and secrets directly as shell variables (e.g., $MY_VAR, $MY_SECRET), since they are all injected into the shell environment and available via the standard shell $ syntax.


name: Complete CI Pipeline
platform: ios
environment:
xcode: "16.4"
triggers:
- push
- pull_request
jobs:
lint:
name: Lint
steps:
- run: swiftlint
build:
name: Build ${{ matrix.scheme }}
needs: [lint]
strategy:
matrix:
scheme: [Release, Debug]
steps:
- run: pod install
- run: xcodebuild build -scheme MyApp -configuration ${{ matrix.scheme }}
test:
name: Test
needs: [build]
steps:
- run: pod install
- run: fastlane test
notify:
name: Notify
if: always()
needs: [test]
steps:
- name: Success notification
if: success()
run: curl https://example.com/notify?status=success
- name: Failure notification
if: failure()
run: curl https://example.com/notify?status=failure

Execution order:

  1. lint runs first (no dependencies)
  2. build runs 2× (Debug + Release) after lint succeeds
  3. test runs after both build variants complete
  4. notify always runs (regardless of test success) with appropriate message

Use the android_sdk field to specify your target Android SDK level.

environment:
flutter: "3.24.0" # Required: specify Flutter version for golden image selection
variables:
KEY: value

Use the flutter field to specify which Flutter SDK to install and use. The agent automatically installs the Flutter SDK based on this field. Supported values:

  • Semver version (e.g., "3.24.0") — Install a specific Flutter version
  • Channel (e.g., stable, beta, master) — Install from a Flutter release channel
  • fvm — Read the version from your repository's .fvmrc file

Once the Flutter SDK is installed, Flutter and Dart are available directly on PATH — use flutter and dart in your run: commands (e.g., flutter build or dart test).

For details, see the Flutter quick start guide.

| Platform | Status | Notes | |----------|--------|-------| | ios | Fully Supported | iPhone and iPad builds with code signing and deploy to TestFlight/App Store | | macos | Fully Supported | Mac app builds with code signing | | android | Fully Supported | Native Android builds with keystore signing, Google Play deploy, and build-number auto-increment | | flutter | Fully Supported | Cross-platform builds via FVM with Apple and Android signing support. Use target_platform to build for iOS, macOS, Android, or web — including macOS code signing (Mac Development, Developer ID, Mac App Store) and .pkg deploys to TestFlight/App Store | | react-native | Fully Supported | Dual iOS/Android builds with automatic Node provisioning, unified signing, Metro caching, and deploys to TestFlight/App Store/Google Play/Firebase |

If your app uses Cloud YAML (configured in the dashboard under App → Cloud YAML), branch-specific configurations override the repository runnerhub.yml for that branch. Cloud YAML is organized per branch, allowing different pipeline configurations for different branches.

Requirements:

  • Your app must have a connected repository to create Cloud YAML entries (so branch names can be validated)

Lock behavior: Each branch's Cloud YAML can be locked independently to prevent modifications until explicitly unlocked via the dashboard. When locked, the backend-stored configuration is used; when unlocked or not configured, the repository file is used.