---
title: "GitHub Integration"
description: "Release and patch with Shorebird in GitHub Actions: the setup action, API key or OIDC sign-in, Android and iOS code signing, flavors and monorepos."
---

The [Setup Shorebird](https://github.com/shorebirdtech/setup-shorebird) GitHub Action allows you to integrate Shorebird into your existing GitHub Workflows.

## Prerequisites

✅ Your app has been initialized with `shorebird init`, and `shorebird.yaml` is committed to your repository.

✅ A Shorebird key for the workflow, as described in [Authentication](#authentication), or a trust rule to [sign in with OIDC](#sign-in-with-oidc-instead-of-a-stored-key).

## Quick start

To integrate Shorebird into your CI, use the `setup-shorebird` action. The `setup-shorebird` action downloads Shorebird and adds it to the system path.

```
name: Shorebird Workflow Example

on:
  workflow_dispatch:

jobs:
  example:
    defaults:
      run:
        shell: bash

    runs-on: ubuntu-latest

    steps:
      # Use the setup-shorebird action to configure Shorebird
      - name: 🐦 Setup Shorebird
        uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true # Optionally cache your Shorebird installation

      # Now we're able to use Shorebird CLI in our workflow
      - name: 🚀 Use Shorebird
        run: shorebird --version
```

In the above workflow, the `setup-shorebird` action is used to configure Shorebird in CI, and subsequent steps can execute any Shorebird commands.

By default, `setup-shorebird` installs the latest stable Shorebird release. To pin a CLI release, pass `shorebird-version` (the version `shorebird --version` prints, such as `1.6.136`). Pinning isn’t recommended: Shorebird’s servers stop accepting old CLI versions over time, so a pinned workflow eventually breaks. To build with a specific Flutter version, pass `--flutter-version` to `shorebird release` instead, or `flutter-version` to the `shorebird-release` action, as in [Create releases](#create-releases).

## 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.

Next, with the key copied, navigate to your GitHub repository secrets via:

`"Settings" -> "Secrets and variables" -> "Actions"`.

Then, click `"New repository secret"` and paste the key:

```
name: SHOREBIRD_TOKEN
secret: <THE KEY YOU COPIED>
```

Now the `SHOREBIRD_TOKEN` can be used in the GitHub workflow to perform authenticated functions such as creating patches 🎉

## Sign in with OIDC instead of a stored key

A workflow can sign in with the short-lived OIDC token GitHub issues to each job, so no Shorebird secret is stored in GitHub and nothing has to be rotated. The job signs in as a service account in your organization and can do what that account’s role allows.

Signing in with OIDC needs Shorebird 1.6.129 or newer. On an older CLI, use an API key in `SHOREBIRD_TOKEN` instead.

Note

Trust rules are available on Business and Enterprise plans. If your organization moves to a plan without them, its existing trust rules keep working and can still be deleted, but new ones cannot be added.

### Add a trust rule

1. In the console, open your organization’s **Settings** and select **Service accounts**. You need the Admin or Owner role to manage [service accounts](/account/service-accounts/).

2. Create a service account for the workflow, or pick an existing one. Its [role](/account/service-accounts/#choose-a-role) decides what the workflow can do: Developer, the default, can create releases and patches, and App Manager can also create, update, and delete apps. A workflow that signs in with OIDC needs no API key.

3. Under **GitHub Actions**, click **Add workflow** and enter:

   * **Owner** and **Repository**: where the workflow lives.
   * **Workflow file**: the path of the workflow file that defines the job, such as `.github/workflows/release.yaml`. If the job is in a reusable workflow, enter that file, not the workflow that calls it. A reusable workflow from another repository cannot sign in.
   * **Environment** (recommended): the [GitHub environment](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) the job runs in. Requiring one lets you add reviewers or branch restrictions in GitHub before a release can run.
   * **Ref pattern** (optional): the branches or tags allowed to sign in, such as `refs/heads/main` or `refs/tags/*`. Leave it empty to accept any ref.

[Trust rules](/account/trust-rules/#what-a-rule-matches) explains how each field is matched. The console then shows your project’s ID, which the workflow needs. It is also the number after `/orgs/` in the console’s address bar, and `shorebird orgs list` lists it (`shorebird account orgs` before 1.6.135).

#### Add a trust rule from the command line

`shorebird service-accounts` (Shorebird 1.6.135 or newer) makes the same changes. Create the service account, then add a trust rule to it. Run these commands signed in with `shorebird login` and with `SHOREBIRD_TOKEN` unset. API keys can’t create, change, or delete service accounts, their keys, or their trust rules.

```
shorebird service-accounts create release --role developer --org-id <project id>
shorebird service-accounts trust add release --org-id <project id> \
  --github-repo acme/app --workflow release.yaml --ref main
```

A rule needs `--ref`, `--environment`, or both, unless you pass `--any-ref`. A rule for any ref and no environment lets anyone who can push a branch sign in. `--ref main` means `refs/heads/main`; to match several refs, give a full ref ending in `*`, such as `refs/tags/*`. The command prints the `permissions` and `env` the workflow needs. Run `shorebird service-accounts trust add --help` for its other options.

See [Manage service accounts from the CLI](/account/service-accounts/#manage-service-accounts-from-the-cli) for the other commands.

### Configure the workflow

The job needs permission to request an OIDC token, the environment the rule names (if any), and `SHOREBIRD_PROJECT_ID` set to your project’s ID. The ID is not a secret.

```
name: Shorebird Release

on:
  workflow_dispatch:

jobs:
  release:
    runs-on: ubuntu-latest
    environment: production # Only if your trust rule names an environment
    permissions:
      contents: read
      id-token: write
    env:
      SHOREBIRD_PROJECT_ID: '123' # Your project's ID from the console

    steps:
      - name: 📚 Git Checkout
        uses: actions/checkout@v7

      - name: 🐦 Setup Shorebird
        uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true

      - name: 🚀 Shorebird Release
        run: shorebird release android
```

Do not set `SHOREBIRD_TOKEN` in a job that signs in with OIDC. When `SHOREBIRD_TOKEN` is set, the Shorebird CLI uses it instead. If you are moving a workflow from an API key to OIDC, remove the `SHOREBIRD_TOKEN` line and the repository secret, then [revoke the API key](/account/api-keys/#revoking-an-api-key).

If the sign-in is refused, the CLI prints the reason, such as `No trust rule matches this workflow`. Check that the repository, workflow file, environment and ref of the job match the trust rule. Jobs started by `pull_request_target` can’t sign in; see [when a sign-in is refused](/account/trust-rules/#when-a-sign-in-is-refused).

## Android code signing

`shorebird release android` and `shorebird patch android` run `flutter build`, so signing works exactly as it does for any Flutter app. Follow [Flutter’s Android signing instructions](https://docs.flutter.dev/deployment/android#sign-the-app), or the [Shorebird version of them](/flutter-concepts/releasing-flutter-apps/android#step-3-configure-signing-in-gradle).

The only difference on CI is that the keystore is not already on the machine. Store it as a base64-encoded repository secret, decode it to a path outside your checkout, and point `key.properties` at that path before the Shorebird step runs.

Without `key.properties`, the release build fails, because the release signing config from the Gradle setup above gets no `storeFile`. An app still on Flutter’s default release config signs with the debug key instead, which the Play Console rejects.

## Create releases

The simplest way to create a release is using the official Shorebird GitHub Actions:

* [Setup Shorebird GitHub Action](https://github.com/marketplace/actions/setup-shorebird).
* [Shorebird Release GitHub Action](https://github.com/marketplace/actions/shorebird-release).

```
name: Shorebird Release

on:
  workflow_dispatch:

env:
  SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

jobs:
  release:
    defaults:
      run:
        shell: bash

    # For iOS, see "Release and patch iOS" below.
    runs-on: ubuntu-latest

    steps:
      - name: 📚 Git Checkout
        uses: actions/checkout@v7

      - name: 🐦 Setup Shorebird
        uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true

      - name: Set up Java
        uses: actions/setup-java@v6
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: 🚀 Shorebird Release
        uses: shorebirdtech/shorebird-release@v1
        with:
          flutter-version: 3.47.9
          platform: android
```

`flutter-version` is required. It’s the Flutter version the release is built with, and every patch to that release is built with it too. Pass a version from `shorebird flutter versions list`, or `latest` for the newest stable Flutter version Shorebird supports. `shorebird release --help` lists the other values.

Tip

The `shorebird-release` action also outputs the release version:

```
- name: 🚀 Shorebird Release
  id: shorebird-release
  uses: shorebirdtech/shorebird-release@v1
  with:
    flutter-version: 3.47.9
    platform: android # or 'ios'

- name: 📝 Output Release Version
  run: echo ${{ steps.shorebird-release.outputs.release-version }}
```

## Create patches

```
name: Shorebird Patch

on:
  workflow_dispatch:

env:
  SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

jobs:
  patch:
    defaults:
      run:
        shell: bash

    # For iOS, see "Release and patch iOS" below.
    runs-on: ubuntu-latest

    steps:
      - name: 📚 Git Checkout
        uses: actions/checkout@v7

      - name: 🐦 Setup Shorebird
        uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true

      - name: Set up Java
        uses: actions/setup-java@v6
        with:
          distribution: 'temurin'
          java-version: '17'

      # Note: all signing information (key.properties, etc.) must be set up on
      # this runner for `shorebird patch android` to work.
      - name: 🚀 Shorebird Patch
        uses: shorebirdtech/shorebird-patch@v1
        with:
          platform: android
          release-version: latest
          track: stable
```

`latest` patches the most recently updated release. If a newer release might still be in store review, pass the exact version of the release your users have instead; see [Patching](/code-push/patch/).

Tip

The `shorebird-patch` action also outputs the patch number:

```
- name: 🚀 Shorebird Patch
  id: shorebird-patch
  uses: shorebirdtech/shorebird-patch@v1
  with:
    platform: android # or 'ios'
    release-version: latest
    track: stable

- name: 📝 Output Patch Version
  run: echo ${{ steps.shorebird-patch.outputs.patch-number }}
```

## Pass more arguments: flavors and monorepos

Both actions take two more inputs:

* `args`: arguments added to the `shorebird release` or `shorebird patch` command, such as `--flavor`, `--target` or `--dart-define`. Arguments after `--` go to `flutter build`.
* `working-directory`: where to run the command, for an app that isn’t at the root of your repository. It defaults to the root.

For example, to release and patch the `production` flavor of an app in `apps/my_app`:

```
- name: 🚀 Shorebird Release
  uses: shorebirdtech/shorebird-release@v1
  with:
    flutter-version: 3.47.9
    platform: android
    working-directory: apps/my_app
    args: --flavor production --target lib/main_production.dart

- name: 🚀 Shorebird Patch
  uses: shorebirdtech/shorebird-patch@v1
  with:
    platform: android
    release-version: 1.2.3+4
    track: stable
    working-directory: apps/my_app
    args: --flavor production --target lib/main_production.dart
```

Give the patch the same `--flavor`, `--target` and `--dart-define` values as the release it patches; see [Apps with flavors](/code-push/guides/flavors/). The `flutter-version`, `release-version` and `track` inputs already pass `--flutter-version`, `--release-version` and `--track`, so leave those out of `args`.

## Release and patch iOS

`shorebird release ios` and `shorebird patch ios` need Xcode, so iOS jobs run on a macOS runner.

### Pin the runner and Xcode for iOS

Build each iOS patch with the same Xcode, on the same runner image, as the release it patches. A different toolchain can make the patch report asset or `Podfile.lock` changes you didn’t make, which stops it in CI; see [Build patches with the same toolchain as the release](/code-push/ci/generic/#build-patches-with-the-same-toolchain-as-the-release).

`macos-latest` moves to a new macOS image, with a new default Xcode, when GitHub updates it. Name a versioned runner instead, such as `macos-26`, and select an Xcode that the image lists in [GitHub’s runner images](https://github.com/actions/runner-images#available-images):

```
jobs:
  release-ios:
    runs-on: macos-26
    env:
      SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
    steps:
      - name: 📚 Git Checkout
        uses: actions/checkout@v7

      - name: 🔨 Select Xcode
        run: sudo xcode-select -s /Applications/Xcode_26.6.app

      - name: 🐦 Setup Shorebird
        uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true

      # Install your signing certificate and profile here; see below.

      - name: 🚀 Shorebird Release
        uses: shorebirdtech/shorebird-release@v1
        with:
          flutter-version: 3.47.9
          platform: ios
          args: --export-options-plist ios/ExportOptions.plist

      - name: 📦 Keep the IPA
        uses: actions/upload-artifact@v7
        with:
          name: ipa
          path: build/ios/ipa/*.ipa
```

Use the same `runs-on` and Xcode in the patch job. Change both only when you create a new release. GitHub can update CocoaPods inside an image without renaming it, so before patching, check that `pod --version` on the runner matches the `COCOAPODS:` line in the release’s `Podfile.lock`; see [Build patches with the same toolchain as the release](/code-push/ci/generic/#build-patches-with-the-same-toolchain-as-the-release).

### iOS code signing

Each job on a GitHub-hosted runner starts on a fresh machine with no signing certificate, so the job has to install your distribution certificate and App Store provisioning profile before the Shorebird step runs. `shorebird patch ios` builds the app the way the release did, so do the same in the patch job, with the same `--export-options-plist`.

GitHub’s guide [Installing an Apple certificate on macOS runners](https://docs.github.com/en/actions/how-tos/deploy/deploy-to-third-party-platforms/sign-xcode-applications) shows how. Store the base64-encoded `.p12` certificate, its password, the base64-encoded profile and a keychain password as repository secrets, then add this step before the Shorebird step:

```
- name: 🔑 Install signing certificate and profile
  env:
    BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
    P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
    BUILD_PROVISION_PROFILE_BASE64:
      ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
    KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
  run: |
    CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
    KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
    PROFILES_DIR="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"

    echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_PATH"
    security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
    security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
    security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
    security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
    security list-keychain -d user -s "$KEYCHAIN_PATH"

    mkdir -p "$PROFILES_DIR"
    echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o "$PROFILES_DIR/app_store.mobileprovision"
```

Xcode 16 and newer read provisioning profiles from `~/Library/Developer/Xcode/UserData/Provisioning Profiles`; older Xcode versions read them from `~/Library/MobileDevice/Provisioning Profiles`.

In Xcode, set the Runner target’s Release configuration to sign manually with that certificate and profile. Then commit an export options file that names the profile, and pass it to `shorebird release ios` with `--export-options-plist`, as in the job above:

ios/ExportOptions.plist

```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>app-store-connect</string>
  <key>signingStyle</key>
  <string>manual</string>
  <key>teamID</key>
  <string>YOUR_TEAM_ID</string>
  <key>provisioningProfiles</key>
  <dict>
    <key>com.example.app</key>
    <string>Your App Store profile name</string>
  </dict>
  <key>manageAppVersionAndBuildNumber</key>
  <false/>
</dict>
</plist>
```

Replace the team ID, bundle ID and profile name with your own. Leave `manageAppVersionAndBuildNumber` false: Shorebird refuses an export options file that sets it to true, because Xcode would then change the build number of the IPA you upload, and patches wouldn’t match the release. If you manage signing with [fastlane match](https://docs.fastlane.tools/actions/match/), run it in the job instead of the step above; see [Fastlane](/code-push/ci/fastlane/).

To build without signing, pass `--no-codesign` instead, as the [generic CI guide](/code-push/ci/generic/#build-without-code-signing-in-restricted-environments) describes, and sign the archive outside CI.

For an example of a fully automated development workflow, see the Development Workflow Guide.

[Development Workflow Guide](/code-push/guides/development-workflow)Learn how to set up a fully automated development workflow with Shorebird.
