---
title: "Complete Code Push guide"
description: "Ship your first over-the-air fix in under 30 minutes."
---

A critical bug ships to production. Users report crashes in your payment flow. Your team has a fix committed and tested within the hour. Then you file for App Store review and wait. Twenty-four to forty-eight hours on a good day, longer if it lands on a weekend. By the time the fix reaches your users, the damage to retention and app store ratings is already done.

React Native teams sidestep this problem by swapping a JavaScript bundle. Flutter teams have historically had no equivalent option. This guide explains why that’s true, how Shorebird’s Code Push solves it at the runtime level, and how to get the full implementation running in your production pipeline.

## Why Flutter can’t do what React Native does

Flutter compiles Dart code to native ARM machine code at build time using [ahead-of-time (AOT) compilation](https://flutter.dev/docs/resources/faq#how-does-flutter-run-my-code-on-android). The output is a compiled snapshot baked directly into the app binary. At runtime, there’s no interpreter between your Dart logic and the hardware.

React Native works differently. The JavaScript logic runs inside a JS engine (Hermes or JSC), loaded from a bundle file at startup. Expo’s EAS Update ships a new bundle to devices over the air because swapping the JS file at the path the runtime reads from is all it takes. The app’s logic layer is just a file on disk.

Flutter has no equivalent file to swap. The `libapp.so` on Android (and its iOS counterpart) is compiled Dart code in binary form. You can’t diff a `.dill` snapshot against a new one and swap it at runtime the way you’d replace a JavaScript bundle. The runtime isn’t built to load updated compiled code dynamically.

This isn’t a missing feature waiting to be added. It’s a direct consequence of AOT compilation, which is also what gives Flutter its consistent frame rate, fast startup, and predictable performance. The same property that makes Flutter fast is what makes swapping code hard.

Solving this requires modifying the Dart runtime to support some form of dynamic code execution, which is what Shorebird does. They [modified both the Dart VM and the Flutter engine](https://shorebird.dev/blog/how-we-built-code-push/) to make genuine diff-based OTA code updates possible on Android and iOS.

## How Shorebird Code Push works

When you build a release with Shorebird, the resulting binary contains the standard AOT-compiled Dart snapshot plus Shorebird’s modified Flutter engine, which includes the updater. On startup, the updater checks Shorebird’s servers for a patch scoped to that specific release version. If no patch is installed, the AOT snapshot runs exactly as it would in a standard Flutter build. Once a patch is installed, the app boots from the patch instead, while the release’s AOT snapshot remains in place as the fallback.

How a patch runs depends on the platform. On Android and desktop, a patch is a complete new compiled snapshot of your Dart code, and it runs natively, just like the release. On iOS, Apple’s rules require downloaded code to be interpreted, so the Dart code you changed runs in an interpreter that Shorebird built and maintains, while the code you didn’t change keeps running natively from the release.

![Launching an app](/assets/app-launch-flow-diagram.png)

The [system architecture documentation](/code-push/system-architecture/) describes this in detail, but the operational model comes down to two concepts you need to keep distinct: [releases](/code-push/release/) and [patches](/code-push/patch/).

A release is a full app build that includes the Shorebird runtime. You build it with the Shorebird CLI, register it with Shorebird’s servers, and release the build to your users as you currently do today. Once users install it, that install is tied to a specific release version in Shorebird’s system.

A patch is a Dart code diff computed against that release. Shorebird compares the new Dart snapshot against the original, produces a binary diff, and uploads only the changed bytes to its CDN.

Patches are version-scoped: a patch built against release `1.2.0` only applies to devices running `1.2.0`. Users still on `1.1.0` won’t receive it until you ship a patch targeting that release.

This model has a hard boundary. Here’s what you can and can not do with patches and releases:

| Can update over the air              | Requires a new store release                    |
| ------------------------------------ | ----------------------------------------------- |
| All Dart and Flutter widget code     | Native Kotlin, Swift, Java, or Objective-C code |
| UI layout, business logic, routing   | Native plugin platform channel implementations  |
| Strings, state management, providers | `AndroidManifest.xml` or `Info.plist` changes   |
| Dart-layer bug fixes                 | New runtime permissions                         |
| Pure-Dart Flutter package upgrades   | Native binary assets                            |
| App configuration managed in Dart    | Changes to native dependencies                  |

If you need a new camera permission, you need a new release. Anything outside pure Dart goes back through the store.

## Setting up Shorebird: From init to first patch

The [Quick Start](/getting-started/) takes an existing Flutter project to a patch running on your own device: install the CLI, run `shorebird init`, create a release, change some Dart code, publish a patch to the `staging` track, test it with `shorebird preview`, and promote it to `stable`. Once you ship the release to the stores, you fix a bug in Dart and publish a patch to the release your users have.

Here’s what that flow looks like: ![Lifecycle of patch releases](/assets/release-patch-lifecycle-diagram.png)

[PushPress](https://shorebird.dev/success-stories/push-press) Senior Mobile Developer Allan Wright described the change in their release cycle: “The speed to release updates has skyrocketed. It used to take four days to two weeks depending on urgency. Now it’s way faster. The reliability of shipping fixes is night and day.”

For teams running GitHub Actions, Shorebird provides [documented CI integration](/code-push/ci/github/) that slots directly into existing workflows. Both `shorebird release` and `shorebird patch` run in CI with standard environment variables for authentication. [Codemagic](/code-push/ci/codemagic/) also has first-class Shorebird support for teams already using it as their build platform.

## Controlling update behavior in-app

By default, patches download in the background and apply on the next cold start. For most bug fixes, this is the right behavior. Users get the fix without any interruption, and you avoid forcing a restart mid-session.

The [`shorebird_code_push`](https://pub.dev/packages/shorebird_code_push) Dart package gives you programmatic control when you need it. Add the dependency:

```
flutter pub add shorebird_code_push
```

Then check for and download updates explicitly:

```
import 'package:shorebird_code_push/shorebird_code_push.dart';

Future<void> checkForUpdate() async {
    // Create an instance of the updater class
    final updater = ShorebirdUpdater();

    final status = await updater.checkForUpdate();

    if (status == UpdateStatus.outdated) {
      try {
        // Perform the update
        await updater.update();
      } on UpdateException catch (error) {
        // Handle any errors that occur while updating.
      }
    }
}
```

Call `checkForUpdate()` from the `initState` of your root widget, or from an `AppLifecycleListener` callback on app resume. Both are reasonable entry points. The root widget approach catches users on every cold start; the lifecycle callback catches returning sessions.

For a patch addressing a security vulnerability or a crash hitting a significant fraction of your users, you can force a restart immediately after download. Wire this through `TerminateRestart` from the [`terminate_restart`](https://pub.dev/packages/terminate_restart) package or your own restart mechanism. Reserve forced restarts for genuine incidents. Most patches don’t qualify, and your users will notice an unexpected restart. [Restarting after an update](/code-push/update-strategies/#restarting-after-an-update) covers how to restart safely.

Unless your `shorebird.yaml` sets [`auto_update: false`](/code-push/shorebird-yaml/#auto_update), Shorebird also checks for and downloads patches on every launch by itself. You can call `checkForUpdate()` and `update()` either way. Set `auto_update: false` when you want your code to decide when patches download:

```
auto_update: false
```

## Advanced patterns: Staged rollouts, rollbacks, custom tracks, and patch signing

High-velocity teams don’t ship patches directly to 100% of users. [WAGUS founder Michael Gallego](https://shorebird.dev/success-stories/wagus) described their patching cadence: “Patches ship fast. One release had 60 patches. Most of them are small quality-of-life updates requested by users. With Shorebird, they could ship them the same day.” Shipping that volume safely requires using a pattern from those discussed below.

### Percentage-based rollouts

You can set up [percentage-based rollout controls](/code-push/guides/percentage-based-rollouts/) using Shorebird’s “tracks” feature and a key-value store like Firebase. With this, you can set up a progression that:

* Starts at 5% and monitors crash reporting (Crashlytics, Sentry, or your current stack) for 60 to 90 minutes
* If error rates stay stable, expands to 25% and checks again
* Moves to 100% after your second checkpoint passes cleanly

Starting at 5% limits your blast radius. If a patch introduces a regression your test suite missed, you catch it at 5% user exposure rather than after it’s affected your entire active user base.

### Rollbacks

When a patch causes a regression, you can use the Shorebird Console to [roll it back](/code-push/rollback/#how-to-roll-back-a-patch). Once you roll back a patch, the installed apps learn about the rollback as soon as a patch check occurs, and it is implemented on the next boot. The [rollback documentation](/code-push/rollback/) covers command syntax and the confirmation flow.

> Rollback takes effect at the infrastructure level immediately. Users receive the previous patch the next time their app checks for updates, with no action required on their end.

### Custom tracks

Shorebird supports named update tracks for staging and beta environments:

```
shorebird patch android --track=beta
```

Beta testers receive the patch immediately while production users remain on the current stable version. This gives you a real staging-to-production pipeline for OTA patches, not just for store submissions.

You choose a device’s track in your Flutter code by passing `track` to `checkForUpdate()` and `update()`, and you can promote a patch from `beta` to `stable` once it’s passed QA. The [tracks documentation](/code-push/tracks/) covers track configuration in full.

### Patch signing

Patch signing ensures that only signed code can run on your users’ devices. The Shorebird runtime verifies each patch’s signature on-device before applying it. If the signature doesn’t match, the patch is rejected.

New releases are signed by default with a key Shorebird manages for your organization in Google Cloud KMS, so an attacker on the network or the delivery path can’t ship a valid patch. You can instead sign with a private key that you generate and control, so that Shorebird never has access to it. This follows the same model used by app stores, where code must be signed by a trusted key before it is allowed to run. The [patch signing guide](/code-push/guides/patch-signing/#what-managed-signing-protects-against) compares what each protects against.

Transport security like TLS protects how patches are delivered. Patch signing protects what is delivered. Together, they ensure that patches arrive securely and haven’t been tampered with.

For teams in fintech, healthcare, or any regulated industry where code changes require an audit trail, signing with your own key is the recommended baseline.

## App Store and Play Store compliance

Shorebird’s approach is compliant with both stores.

[Apple’s App Store Review Guideline 2.5.2](https://developer.apple.com/app-store/review/guidelines/#software-requirements) says apps may not “download, install, or execute code which introduces or changes features or functionality of the app”. The terms for downloaded code are in the [Apple Developer Program License Agreement, section 3.3.1(B)](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/#b331): an app may not download executable code, but it may download interpreted code as long as that code doesn’t change the app’s primary purpose, doesn’t bypass signing, sandbox, or other security features of the OS, and doesn’t create a store or storefront for other apps.

Shorebird’s mechanism fits within those terms. Patched code runs through the Shorebird Dart interpreter, not as loaded native code. Shorebird maintains a [documented compliance position](/code-push/faq/#store-compliance) covering the boundary in detail.

The practical rule: use OTA patches for bug fixes, performance improvements, and iteration on existing features. More broadly, avoid shipping changes that would surprise users or that App Review wouldn’t approve. That’s not unique to Shorebird; it applies to any interpreted-code OTA mechanism on iOS.

[Google Play’s Device and Network Abuse policy](https://support.google.com/googleplay/android-developer/answer/9888379) has a similar rule: an app may not download executable code from a source other than Google Play, but the restriction does not apply to code that runs in a virtual machine or an interpreter that provides indirect access to Android APIs. Standard policy compliance applies too, such as not changing the app’s purpose through an update. The [Play Store compliance FAQ](/code-push/faq/#does-shorebird-comply-with-play-store-guidelines) covers how Shorebird meets both.

If your team operates in a regulated industry, document each patch with a short description of what changed. The Shorebird console stores version history for every patch across every release, and on the Enterprise plan the [audit log](/enterprise/audit-logs/) records who created, promoted, or rolled back each patch. Pair that record with your existing change management process and you have the audit trail most compliance frameworks require.

## Your next step: Push a patch in the next 30 minutes

Follow the [Quick Start](/getting-started/) to go from a clean Flutter project to a patch running on your own device, without a store submission.

Shorebird’s free Hobby plan supports the complete Code Push workflow, including the Shorebird console and patch delivery, up to its monthly patch install limit. That’s enough to run a full end-to-end proof of concept without a payment method on file. See [Plans](/account/plans/).

## Related

[System Architecture](/code-push/system-architecture)Overview of Shorebird's components and source code.

[Troubleshooting](/code-push/troubleshooting)How to resolve common issues.
