CI/CD & App Store Deployment

Over-the-Air Updates with Shorebird Code Push

16 min Lesson 12 of 12

Over-the-Air Updates with Shorebird Code Push

Shipping a bug fix through the App Store or Google Play can take hours to days. Shorebird Code Push solves this by letting you push Dart-only patches directly to users' devices without going through a store review cycle. This lesson explains how Shorebird works under the hood, how to wire it into your CI pipeline, and what you must know about store-guideline compliance before you ship.

How Shorebird Works

Shorebird is built around a modified Dart VM that ships inside your release build. When your app starts, the Shorebird runtime checks for a patch available on the Shorebird servers. If a patch exists, it is downloaded and applied in the background; the next launch runs the patched code. The mechanism is analogous to React Native's CodePush but operates at the compiled Dart bytecode level — no JavaScript involved.

  • Dart VM patching: Only Dart code changes are deliverable. Native code (Kotlin, Swift, platform channels) always requires a full store submission.
  • No full binary re-download: Patches are differential — only changed Dart snapshots are transferred, keeping patch sizes small (often tens of kilobytes).
  • Atomic rollout: Patches are staged by percentage, so you can roll out to 5 % of users before widening.
Note: Shorebird does not modify native binaries. If your fix touches platform-specific code, a plugin's native layer, or app permissions, you must ship a full store release instead.

Installing the Shorebird CLI

The shorebird CLI wraps the Flutter toolchain. Install it once per machine:

Install Shorebird CLI

# macOS / Linux — one-liner via curl
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash

# Verify installation
shorebird --version

# Authenticate with your Shorebird account
shorebird login

After installing, initialise Shorebird inside your Flutter project. This adds a shorebird.yaml file and injects the Shorebird engine override into your project:

Initialise a Flutter App with Shorebird

# Run from your project root
shorebird init

# shorebird.yaml is created — commit this file
# It contains your app_id that links releases to your Shorebird dashboard

# Build a Shorebird-enabled release APK (Android)
shorebird release android

# Build a Shorebird-enabled release IPA (iOS)
shorebird release ios

# Create a patch after changing Dart code (no store submission needed)
shorebird patch android
shorebird patch ios

Integrating Shorebird into a CI Pipeline

A typical GitHub Actions workflow separates the full store release from subsequent patch-only deployments. Below is a minimal but production-representative example:

GitHub Actions — Patch Workflow (shorebird_patch.yml)

name: Shorebird Patch

on:
  push:
    branches: [patch/**]   # push to patch/fix-login triggers a patch

jobs:
  patch:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.0'
          channel: stable

      - uses: shorebirdtech/setup-shorebird@v1
        with:
          shorebird-version: latest

      - name: Authenticate Shorebird
        run: echo "$SHOREBIRD_TOKEN" | shorebird login --ci
        env:
          SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

      - name: Apply Android patch
        run: shorebird patch android --release-version=1.2.3+45

      - name: Apply iOS patch
        run: shorebird patch ios --release-version=1.2.3+45
Tip: Store SHOREBIRD_TOKEN as an encrypted GitHub Actions secret. Generate the token from the Shorebird console under Account → API Keys. Never hard-code it in your workflow YAML.

Update Policies and Rollbacks

The shorebird_code_push Flutter package exposes an UpdateTrack enum and update-check methods that you can call from application code to control when and how updates are applied:

Checking for Updates at Runtime

import 'package:shorebird_code_push/shorebird_code_push.dart';

final _updater = ShorebirdUpdater();

Future<void> checkForUpdate() async {
  final status = await _updater.checkForUpdate();

  if (status == UpdateStatus.outdated) {
    // Download patch in the background; applied on next cold start
    await _updater.update();
    debugPrint('Patch downloaded — restart to apply.');
  }
}

// Immediate (hard) update — forces app restart after download
Future<void> forceUpdate() async {
  final result = await _updater.update(
    track: UpdateTrack.stable,
  );
  if (result == UpdateResult.upToDate) return;
  // Prompt the user to restart or call SystemNavigator.pop()
}

Rolling back a bad patch is a one-command operation from the CLI or a single click in the Shorebird web console:

Rolling Back a Patch

# List patches for a release
shorebird patch list --release-version=1.2.3+45

# Roll back patch number 3
shorebird patch rollback --patch-number=3 --release-version=1.2.3+45

# After rollback, affected devices revert on the next launch
# No action required from users
Warning: A rollback does not instantly reach every device. Users must open the app while connected to the internet to receive the rollback signal. Design your hotfix strategy so that rollbacks are a safety net, not a primary deployment mechanism.

Store Guideline Compliance

Both Apple and Google permit OTA code updates, but with restrictions. Violating these rules can result in app removal:

  • Apple App Store: Section 3.3.2 of the Developer Program License Agreement prohibits downloading and executing native code. Because Shorebird only patches Dart bytecode interpreted by the embedded Dart VM, it complies with this rule — no new native machine code is introduced.
  • Google Play: Play's Device and Network Abuse policy restricts running downloaded code that changes app behaviour in a deceptive way. Shorebird is used to deliver bug fixes and minor improvements, not to swap out core functionality or bypass the review process for significant feature additions.
  • Safe use cases: text copy changes, bug fixes, performance improvements, minor UI tweaks.
  • Prohibited use cases: adding new platform permissions, changing the app's core purpose, bypassing content review for major features.
Best practice: Keep a changelog in your Shorebird dashboard that describes every patch. This creates an audit trail that demonstrates your patches are compliant fixes rather than feature additions that circumvent review.

Summary

Shorebird Code Push gives Flutter teams the ability to ship Dart-only bug fixes and minor updates directly to users, bypassing the store review cycle. The CLI integrates cleanly into GitHub Actions and other CI systems. Patch delivery is differential and staged, and rollbacks are instant from the dashboard. The key constraint is that only Dart code is patchable — native layer changes always require a full store release. Used responsibly within Apple and Google's guidelines, Shorebird is a powerful addition to a mature Flutter deployment pipeline.