A real pain point for mobile apps is waiting days or weeks for App Store and Play Store reviews whenever you need to ship a fix or a small update to users.
That exact problem is what we are solving in this article. I’ll show you exactly how to automate your Flutter releases and patches using Shorebird and GitHub Actions, so you can push updates to users in minutes instead of waiting for store review.
We are going to create a clean, production-ready CI pipeline for both full releases and instant code-push updates.
Prerequisites
Before we start, here’s what you need:
- A Shorebird account and an app already set up with Shorebird
- Shorebird CLI installed and logged in on your machine
- A Flutter project that already has Shorebird initialized
- A GitHub repository
- Basic understanding of GitHub Actions
If you haven’t set up Shorebird in your Flutter app yet, check out my video on YouTube on how to set up Shorebird. This article assumes that’s already done.
Authentication Setup
The first thing we need is authentication so GitHub Actions can talk to Shorebird.
Go to the Shorebird Console → Account → API Keys → Create API Key.
Give it a clear name, e.g “GitHub Actions”, choose an expiration, and set the required permissions.
Copy the key immediately because you won’t see it again.
Now go to your GitHub repository → Settings → Secrets and variables → Actions → New repository secret.
Name it exactly: SHOREBIRD_TOKEN
Paste the key and save.
This token will be available in all your workflows as ${{ secrets.SHOREBIRD_TOKEN }}.
That’s the only authentication step you need.
Official Shorebird GitHub Actions
Shorebird provides three official GitHub Actions that make life much easier:
- shorebirdtech/setup-shorebird@v1 — installs Shorebird on the runner
- shorebirdtech/shorebird-release@v1 — creates a release
- shorebirdtech/shorebird-patch@v1 — creates a patch
I strongly recommend using these instead of calling the CLI manually. They’re cleaner and maintained by the Shorebird team.
Release Workflow
Let’s build the release workflow first.
Create a new file in your project: .github/workflows/shorebird-release.yml
Here’s the structure:
- Trigger on version tags: v1.0.0, v1.2.3, etc. or manually trigger the workflow
- Set the SHOREBIRD_TOKEN as an environment variable
- Pin your Flutter version (very important)
- Checkout the code
- Set up Java for Android (or Xcode signing for iOS)
- Set up Shorebird with caching
- Decode your keystore/certificates from secrets
- Run the official shorebird-release action
- Upload the APK, AAB, or IPA as artifacts
I’ll show both Android and iOS versions.
For Android, the key steps are decoding the keystore and creating the key.properties file from secrets.
GitHub Secrets You Need to Add for Android
Go to GitHub → repo → Settings → Secrets and variables → Actions → New repository secret
Secret name - Value
ANDROID_KEY_ALIAS - your_key_alias
ANDROID_KEY_PASSWORD - your_key_password
ANDROID_STORE_PASSWORD - your_store_password
ANDROID_KEYSTORE_BASE64 - run `base64 -i ~/path/to/upload.jks | tr -d '\n' | pbcopy`
The keystore base64 will be copied to your clipboard; paste it into GitHub Secrets
GitHub Secrets You Need to Add for iOS
Go to GitHub → repo → Settings → Secrets and variables → Actions → New repository secret
Secret name - Value
IOS_CERTIFICATE_BASE64 - base64 of your .p12
IOS_CERTIFICATE_PASSWORD - Password you set when exporting the .p12
IOS_PROVISIONING_PROFILE_BASE64 - base64 of com.example.app profile
For iOS, you need to import the certificate and provisioning profile into a temporary keychain.
# IOS_CERTIFICATE_BASE64 - export your .p12 from Keychain Access first, then:
base64 -i ~/path/to/distribution.p12 | tr -d '\n' | pbcopy
# IOS_PROVISIONING_PROFILE_BASE64 - download from Apple Developer Portal
base64 -i ~/path/to/YourApp_AppStore.mobileprovision | tr -d '\n' | pbcopy
You can check out this document on how to create an Apple Distribution Certificate.
Once this workflow finishes, you get signed artifacts ready to upload to the stores.
This is your normal full release path.
name: Shorebird Release
on:
workflow_dispatch:
inputs:
platform:
description: 'Target platform'
required: true
default: 'both'
type: choice
options:
- android
- ios
- both
jobs:
release_android:
name: Release Android
if: ${{ github.event.inputs.platform == 'android' || github.event.inputs.platform == 'both' }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
cat > android/key.properties <<EOF
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=keystore.jks
EOF
shell: bash
- name: Setup Shorebird
uses: shorebirdtech/setup-shorebird@v1
- name: Shorebird Release (Android)
uses: shorebirdtech/shorebird-release@v1
id: shorebird-release
with:
flutter-version: latest
platform: android
env:
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
- name: Print Release Version
run: echo "Released version ${{ steps.shorebird-release.outputs.release-version }}"
shell: bash
Full release on GitHub Gist.
Patch Workflow — The Real Power
Now, the part that actually lets you skip App Store review.
Create another file: .github/workflows/shorebird-patch.yml
name: Shorebird Patch
on:
push:
branches:
- main
workflow_dispatch:
inputs:
platform:
description: 'Target platform'
required: true
default: 'both'
type: choice
options:
- android
- ios
- both
release-version:
description: 'Release version to patch (e.g. 1.0.0+1). Defaults to latest.'
required: false
default: 'latest'
jobs:
patch_android:
name: Patch Android
if: ${{ github.event_name == 'push' || github.event.inputs.platform == 'android' || github.event.inputs.platform == 'both' }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
cat > android/key.properties <<EOF
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=keystore.jks
EOF
shell: bash
- name: Setup Shorebird
uses: shorebirdtech/setup-shorebird@v1
- name: Shorebird Patch (Android)
uses: shorebirdtech/shorebird-patch@v1
id: shorebird-patch
with:
platform: android
release-version: ${{ github.event.inputs.release-version || 'latest' }}
args: --allow-asset-diffs --allow-native-diffs
env:
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
- name: Print Patch Number
run: echo "Patch number ${{ steps.shorebird-patch.outputs.patch-number }}"
shell: bash
Full release on GitHub Gist.
Trigger this one on hotfix tags, for example: v1.0.0-hotfix.1
The structure is almost identical to the release workflow, but we use the shorebird-patch action instead.
Important points:
- A patch can only be applied to an existing release
- Use the — staging flag first so you can preview the update
- After testing with shorebird preview — track=staging, promote it to production from the Shorebird Console.
This is the real magic. Users get the fix over-the-air (OTA) in minutes. No waiting for review. No forcing them to download a new version from the store.
Recommended Development Workflow
Here’s the clean workflow I recommend (and the one Shorebird documents):
- Work on features and fixes on feature branches
- Open a pull request into main
- CI runs formatting, analysis, and tests
- Squash and merge into main (main stays always releasable)
- When you’re ready for a full release → create a tag like v1.2.0 → the release workflow runs automatically
- If a critical bug appears → fix it on main → cherry-pick into the release branch → create a hotfix tag like v1.2.0-hotfix.1 → the patch workflow runs
- Preview on staging → promote to production
This keeps everything automated, clean, and low-risk.
Best Practices
A few important tips:
- Always pin the Flutter version on releases using — flutter-version
- Use — dry-run on pull requests so you catch build issues early without creating real releases or patches
- Keep your SHOREBIRD_TOKEN secure — never hardcode it
- For iOS environments without certificates, you can use — no-codesign (then sign later)
- Shorebird automatically runs in non-interactive mode in CI when the token is present
Follow these, and your pipeline will be solid.
Resources
All of this is based on the official Shorebird documentation:
I’m also a Shorebird Ambassador, so if you have questions, drop them in the comments.
That’s it.
You now have a complete automated system:
- Full releases go through the stores as usual
- Critical updates and bug fixes go live to users in minutes via Shorebird
No more waiting for review every single time.
If this was useful, hit the like button, subscribe, and turn on notifications so you don’t miss the next post.
If you want personalized help setting this up for your project, you can send a DM.
I’ll see you in the next one. Peace.




Top comments (0)