---
title: "Fastlane"
description: "Release and patch Android, iOS and macOS apps with Shorebird from fastlane lanes, on CI or on your own machine."
---

Fastlane is a popular tool for automating the build and release process for iOS, Android and macOS apps. Shorebird can be integrated with fastlane to automate releasing and patching.

## Installation

Follow the setup instructions on the fastlane website to install fastlane ([ios](https://docs.fastlane.tools/getting-started/ios/setup/), [android](https://docs.fastlane.tools/getting-started/android/setup/)). For macOS, follow the iOS instructions from your app’s `macos` directory.

## Using Fastlane on CI

This section assumes that you are using fastlane on CI to release your app. If you are running fastlane locally (on your development machine), you can skip to the [Using fastlane locally](#using-fastlane-locally) section.

A working example of this setup can be found in the [fastlane\_demo](https://github.com/shorebirdtech/samples/tree/main/fastlane_demo) sample.

On the CI machine, install Shorebird and add it to the `PATH` as the [generic CI guide](/code-push/ci/generic/#installing-shorebird) shows, and set `SHOREBIRD_TOKEN` as described in [Authentication](#authentication). Fastlane runs `shorebird` as a child process, so the CLI reads `SHOREBIRD_TOKEN` from the CI job’s environment.

For code signing and uploading to the stores on CI, follow fastlane’s own [continuous integration guide](https://docs.fastlane.tools/best-practices/continuous-integration/). For iOS, [match](https://docs.fastlane.tools/actions/match/) keeps your certificates and provisioning profiles in a store your whole team and CI share; call [`setup_ci`](https://docs.fastlane.tools/actions/setup_ci/) before `match` so it has a keychain to install into.

Notice that the demo app’s [`Fastfile`](https://github.com/shorebirdtech/samples/blob/main/fastlane_demo/ios/fastlane/Fastfile) has a `release_shorebird` lane. It uses the `shorebird_release` action **in place of** fastlane’s [`build_app`](https://docs.fastlane.tools/actions/build_app/) action to build an ipa and create a Shorebird release, then uploads it to TestFlight with `upload_to_testflight`.

Note that this change from `build_app` to `shorebird_release` is the only change needed to add Shorebird to your fastlane workflow.

### Patching

To patch your app, you can use the `shorebird_patch` action. You can see an example of this in the `patch_shorebird` lane in the [`Fastfile`](https://github.com/shorebirdtech/samples/blob/main/fastlane_demo/ios/fastlane/Fastfile#L145-L185).

For iOS, build each patch with the same Xcode as the release it patches; see [Build patches with the same toolchain as the release](/code-push/ci/generic/#build-patches-with-the-same-toolchain-as-the-release).

### Authentication

Creating releases and patches needs a signed-in Shorebird CLI. In CI, the CLI reads an API key from the `SHOREBIRD_TOKEN` environment variable, so nobody has to run `shorebird login` on the builder.

Use a key that belongs to a [service account](/account/service-accounts/). A service account belongs to your organization rather than to a person, so its key keeps working when whoever set up CI leaves or changes roles, and the audit log shows the job’s actions under the service account’s name. Service accounts are available on every plan. To create one, you need the Admin or Owner role in the organization:

1. In the [Shorebird Console](https://console.shorebird.dev), open your organization’s **Settings** and select **Service accounts**.
2. Click **Create service account**, name it after the CI system that will use it (such as `GitHub Actions`), and click **Create**.
3. Keep the **Developer** role, which can create releases and patches. Choose **App Manager** instead if the job also creates apps, for example by running `shorebird init`.
4. Under **API keys**, click **Create key**, enter a name, and choose an expiration.
5. Copy the key. It is shown only once.

Store the key as `SHOREBIRD_TOKEN` in your CI platform’s secrets manager, as the steps below show.

Caution

`SHOREBIRD_TOKEN` is a secret. Do not check it into source control or share it publicly.

If you can’t create a service account, a personal API key from **Account → API Keys** goes in `SHOREBIRD_TOKEN` the same way. It acts as you, can do at most what your own role allows, and stops working if your account is deleted. See [API Keys](/account/api-keys/).

Migrating from `shorebird login:ci`

Tokens from `shorebird login:ci` stopped working on October 1, 2026. Replace the value of your `SHOREBIRD_TOKEN` secret with a new key; the variable name and your workflow files stay the same.

Add the key to your CI system’s secrets as `SHOREBIRD_TOKEN`, and pass it to the job that runs fastlane as an environment variable.

## Using Fastlane locally

### Setup

This section assumes that you have Shorebird installed on the machine that will be running fastlane. If you have not already installed Shorebird, you can do so by following the [Getting Started instructions](/getting-started).

If you are not already using fastlane with your project, navigate to your app’s `ios` directory in your project and run `fastlane init`. You will be prompted to answer several questions. For this guide, choose to manually add lanes to the `Fastfile`.

### Use the Shorebird plugin

Run the following command to install the `shorebird` fastlane plugin, which exposes the `shorebird_release` and `shorebird_patch` actions.

```
bundle exec fastlane add_plugin shorebird
```

### Update Fastfile to release

Open the `Fastfile` in the `fastlane` directory and add the following lane:

```
lane :release_shorebird do
  shorebird_release(platform: "ios")
  upload_to_testflight
end
```

To run this, execute the following command:

```
bundle exec fastlane release_shorebird
```

Note

To upload to TestFlight, you will need to create an Apple app-specific password. You can do this at <https://appleid.apple.com/account/manage>. Once you have created the password, create a `.env` file in your fastlane directory and add `FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=your_password_here`.

If you would like to provide additional arguments to the release command, you can do so using the `args` parameter. For example:

```
shorebird_release(platform: "ios", args: "--no-codesign --build-name=1.0.0")
```

### Update Fastfile to patch

Open the `Fastfile` in the `fastlane` directory and add the following lane:

```
lane :patch_shorebird do |options|
  release_version = options[:release_version] or
    UI.user_error!("Pass the release to patch, e.g. release_version:1.0.0+1")
  shorebird_patch(platform: "ios", args: "--release-version=#{release_version} --track=stable")
end
```

Run it with the version of the release to patch, normally the one in the App Store:

```
bundle exec fastlane patch_shorebird release_version:1.0.0+1
```

Naming the release keeps a patch from landing on a build that hasn’t shipped, such as one still in TestFlight. See [Patching](/code-push/patch/) for why to always pass `--release-version`.

As with `shorebird_release`, you can provide additional arguments to the patch command using the `args` parameter.

```
shorebird_patch(platform: "ios", args: "--release-version=1.0.0+1 --track=stable --allow-native-diffs")
```

## Android

The same actions release and patch Android apps. Run `fastlane init` in your app’s `android` directory, which sets up the Play Console credentials that [`upload_to_play_store`](https://docs.fastlane.tools/actions/upload_to_play_store/) uses, and add the Shorebird plugin there as above. Then add these lanes to `android/fastlane/Fastfile`:

```
default_platform(:android)

platform :android do
  lane :release_shorebird do
    shorebird_release(platform: "android")
    upload_to_play_store(
      track: "internal",
      aab: File.expand_path("../../build/app/outputs/bundle/release/app-release.aab")
    )
  end

  lane :patch_shorebird do |options|
    release_version = options[:release_version] or
      UI.user_error!("Pass the release to patch, e.g. release_version:1.0.0+1")
    shorebird_patch(platform: "android", args: "--release-version=#{release_version} --track=stable")
  end
end
```

`shorebird release android` signs the app bundle with your Gradle release signing config, as `flutter build appbundle` does, so the machine running the lane needs your upload keystore and `key.properties`. Ruby code in a lane runs from the `fastlane` directory, so `File.expand_path("../..")` is your Flutter project’s root, where the app bundle is built.

With a flavor, pass it in `args` to both actions, for example `shorebird_release(platform: "android", args: "--flavor production")`, and change the app bundle path to `build/app/outputs/bundle/productionRelease/app-production-release.aab`.

```
bundle exec fastlane android release_shorebird
bundle exec fastlane android patch_shorebird release_version:1.0.0+1
```

## macOS

The `shorebird_release` and `shorebird_patch` actions are written for iOS and Android, so these lanes call the Shorebird CLI with `sh` instead. They assume the `Fastfile` is in your app’s `macos/fastlane` directory. Fastlane runs `sh` and other Ruby code in a lane from the `fastlane` directory, so `File.expand_path("../..")` is your Flutter project’s root.

`shorebird release macos` runs `flutter build macos`, which builds and signs the app with the signing settings of your Xcode project’s Release configuration, then creates the release. It doesn’t archive or export the app, so Xcode never re-signs it for distribution the way it does when exporting an archive. Set up the Release configuration for how you distribute the app, as described below, before releasing. The signed `.app` is in `build/macos/Build/Products/Release`.

To pass arguments to `shorebird release` or `shorebird patch`, add them to the `sh` call, for example `sh("shorebird", "release", "macos", "--flavor", "production")`. With a flavor, the app is in `build/macos/Build/Products/Release-production`, so change the `Release/*.app` glob in the release lanes to match.

The lanes are in a `platform :mac` block. `fastlane init` writes a `Fastfile` for iOS (`default_platform(:ios)` and a `platform :ios` block), so either change it to `default_platform(:mac)` or name the platform when you run a lane:

```
bundle exec fastlane mac release_shorebird
bundle exec fastlane mac patch_shorebird release_version:1.0.0+1
```

The lanes expect your signing certificates in the keychain. If you manage them with [match](https://docs.fastlane.tools/actions/match/), as the iOS setup above does, each section names the `match` call to add to the lane before the release step. On CI, call `setup_ci` before `match` so it has a keychain to install into.

Note

Apple re-signs apps it distributes through the Mac App Store, and the lane for releasing outside the Mac App Store re-signs the app after it is built. Patches install on a re-signed app when the release was built with Shorebird 1.6.128 or newer on its default Flutter (3.47.7 at revision `5bef02a` or later, or a newer Flutter). For a release built with an older Shorebird, run `shorebird upgrade` and create a new release.

### Release outside the Mac App Store

For an app you distribute yourself, set the Release configuration to sign manually with your Developer ID Application certificate. With match, add `match(type: "developer_id", platform: "macos")`.

Apple’s notary service requires the app to be signed with the hardened runtime and every executable in it to carry a secure timestamp. Xcode adds the timestamp only when it exports an archive, and Flutter signs `App.framework` and `FlutterMacOS.framework` without one. So this lane re-signs the app’s frameworks and then the app with both, and notarizes the result with fastlane’s [`notarize`](https://docs.fastlane.tools/actions/notarize/) action:

```
platform :mac do
  lane :release_shorebird do
    project_root = File.expand_path("../..")
    sh("shorebird", "release", "macos")
    app = Dir.glob(File.join(project_root, "build/macos/Build/Products/Release/*.app")).first
    codesign = ["codesign", "--force", "--timestamp", "--options", "runtime",
                "--sign", "Developer ID Application: Your Team (TEAMID)"]
    Dir.glob(File.join(app, "Contents/Frameworks/*")).each { |framework| sh(*codesign, framework) }
    sh(*codesign, "--preserve-metadata=entitlements", app)
    notarize(package: app, api_key_path: File.expand_path("api_key.json"))
  end
end
```

Replace the signing identity with your own; `security find-identity -v` lists the identities in your keychain. `notarize` uploads the app to Apple’s notary service and staples the ticket to it. It takes an App Store Connect API key (`api_key_path` or `api_key`; this example reads `api_key.json` from the `fastlane` directory) or an Apple ID (`username`). Package the stapled app however you ship it, for example in a disk image.

### Release to the Mac App Store

A build you can upload to the Mac App Store needs:

* The Release configuration set to sign manually with an Apple Distribution certificate.
* A Mac App Store provisioning profile for your app’s bundle ID, selected in the Release configuration. The build embeds it in the app, and App Store Connect rejects a Mac build without one (ITMS-90889).
* A Mac Installer Distribution certificate, which `productbuild` uses to sign the package. It shows in your keychain as “3rd Party Mac Developer Installer”.

Automatic signing doesn’t produce this build. With it, the build signs with an Apple Development certificate, because Xcode applies the distribution certificate and profile only when it exports an archive.

With match, add `match(type: "appstore", platform: "macos", additional_cert_types: ["mac_installer_distribution"])`, which installs both certificates and a profile named `match AppStore <bundle ID> macos` to select in the Release configuration.

This lane packages the app with `productbuild` and uploads the package to TestFlight with [`upload_to_testflight`](https://docs.fastlane.tools/actions/upload_to_testflight/), which leaves your App Store version alone:

```
platform :mac do
  lane :release_shorebird do
    project_root = File.expand_path("../..")
    sh("shorebird", "release", "macos")
    app = Dir.glob(File.join(project_root, "build/macos/Build/Products/Release/*.app")).first
    pkg = File.join(project_root, "build/macos/App.pkg")
    sh("productbuild", "--component", app, "/Applications",
       "--sign", "3rd Party Mac Developer Installer: Your Team (TEAMID)", pkg)
    upload_to_testflight(
      app_platform: "osx",
      pkg: pkg,
      api_key_path: File.expand_path("api_key.json")
    )
  end
end
```

Replace the installer identity with your own.

### Patch

```
platform :mac do
  lane :patch_shorebird do |options|
    release_version = options[:release_version] or
      UI.user_error!("Pass the release to patch, e.g. release_version:1.0.0+1")
    sh("shorebird", "patch", "macos", "--release-version=#{release_version}", "--track=stable")
  end
end
```

Pass the release to patch as `release_version`, as shown above. `shorebird patch macos` builds and signs the app with the Release configuration’s signing settings, as the release did, so the machine running this lane needs the same certificate and provisioning profile. With match, add the same `match` call the release lane uses, with `readonly: true`, before `shorebird patch` (after `setup_ci` on CI). Build the patch the way you built the release: passing `--no-codesign` to a patch of a signed release makes Shorebird report native code changes that aren’t in your code. A patch needs no re-signing, notarization or upload; Shorebird delivers it to apps already installed from the release.
