Getting Started

Everything you need to deploy over-the-air updates to your Flutter apps with FlutterPlaza Code Push.

What is Code Push?

Code Push allows you to push updates to your deployed Flutter applications without requiring users to download a new version from the app store. When your app starts, it checks for available patches and applies them on the fly.

This is useful for:

  • Bug fixes — Ship a critical fix in minutes, not days.
  • UI tweaks — Adjust layouts, copy, or styles without a full release cycle.
  • A/B testing — Roll out changes to a subset of users and measure impact.
  • Compliance — Instantly update content or behaviour to meet regulatory changes.
How it works: When you create a release, a baseline snapshot of your compiled Dart code is uploaded. When you later run fcp codepush patch, a binary diff is computed between your current code and the baseline. The device downloads only the diff and applies it at runtime.

Prerequisites

Before you begin, make sure you have the following installed:

RequirementMinimum VersionCheck Command
Flutter SDK3.22.0flutter --version
Dart SDK3.4.0dart --version
fcp CLIlatestfcp --version

Installation

Install the fcp CLI via the flutter_compile Dart package:

dart pub global activate flutter_compile

Make sure the Dart global bin directory is on your PATH. If you have never activated a global package before, Dart will print the path you need to add. On macOS/Linux this is typically:

export PATH="$HOME/.pub-cache/bin:$PATH"

Verify the installation:

fcp --version

Quick Start

Follow these five steps to go from zero to your first over-the-air patch.

1. Set up build tools

Code Push downloads a small set of build tools that match your Flutter SDK version. The setup command auto-detects your version and fetches everything it needs from our CDN.

fcp codepush setup
Note: Re-run this command each time you upgrade your Flutter SDK version.

2. Log in

Authenticate from the CLI. This opens your browser — sign in or create an account, then click Authorize:

fcp codepush login

The CLI opens your browser automatically. Click Authorize and the CLI receives your session. No API key needed. Credentials are stored locally at ~/.flutter_compilerc.

3. Initialize your project

fcp codepush init

This creates an app on the server (auto-reads the name from pubspec.yaml) and returns an app ID used in subsequent commands.

4. Build a release

A release represents the compiled baseline of your app at a specific version. Run this from your Flutter project root:

fcp codepush release --build --platform apk --version 1.0.0

This builds your app, captures the baseline, and uploads it to the Code Push server. Note the release ID that the command prints — you will need it to push patches against this release.

5. Push a patch

After making changes to your Dart code, push a patch against the release:

fcp codepush patch --build --platform apk --release-id <release-id>

The CLI builds your updated app, diffs it against the baseline, signs the result with your RSA key, and uploads it. The patch becomes available to all devices connected to the matching release.

Done! Your users will receive the patch the next time they restart the app (or on the next update check if you have configured foreground updates).

CLI Reference

Complete reference for every fcp command, including usage, flags, and examples.

Global flags: All commands accept --verbose for detailed output and --help for usage information.

fcp codepush login

Authenticate with the Code Push server. Opens your browser for secure sign-in — no API key needed in the terminal.

fcp codepush login

The CLI opens your browser to the authorization page. Sign in (or create an account) and click Authorize. The CLI detects the approval automatically and stores your session.

Flags

FlagRequiredDescription
--api-keyNoSkip the browser flow and authenticate with an API key directly.
--serverNoServer URL. Defaults to https://codepush.flutterplaza.com.

Example (browser flow)

$ fcp codepush login
Opening browser for authentication...

Your authorization code: push-bolt-742

Browser opened. Authorize the CLI there.

⠋ Waiting for authorization...
✓ Authenticated successfully
  Email: dev@mycompany.com
  Tier:  pro

fcp codepush init

Initialize code push for your Flutter project. Creates an app on the server (reads the name from pubspec.yaml), stores the app ID locally, and — on CLI 0.19.8 and later — generates an RSA‑2048 signing keypair and registers the public key with the server in the same request so patch-signature enforcement is on from your very first patch.

fcp codepush init

Flags

FlagRequiredDescription
--app-idNoUse an existing app ID instead of creating a new one.

Example

$ fcp codepush init
Creating app "my_flutter_app"...
  App ID: app_k8x2m9f1
  App ID saved to config.
Generating RSA signing key pair...
  Private key: ~/.flutter_codepush/codepush_private.pem
  Public key:  ~/.flutter_codepush/codepush_public.pem
  Public key was uploaded with this app, signature verification is enabled.
Upgrading from a pre-0.19.8 CLI? Your existing app still works, but the server has no public key on file for it yet. Run fcp codepush keys register once to finish the migration. See Patch Signing & Migration.

fcp codepush keys

Manage RSA signing keys for code push. Added in flutter_compile 0.19.8 as a focused, server-independent alternative to init’s key-generation step. Use this group if you need to (re)generate keys or register them with the server after the fact.

fcp codepush keys generate

Create a local RSA‑2048 keypair under ~/.flutter_codepush/ and store the private-key path in ~/.flutter_compilerc. Idempotent: refuses to overwrite an existing key unless --force is passed.

fcp codepush keys generate
FlagRequiredDescription
--output-dirNoDirectory to write the keypair into. Defaults to ~/.flutter_codepush/.
--forceNoOverwrite any existing keypair at the output path. Invalidates all previously signed patches.

fcp codepush keys register

Upload the public key to the code push server for the current app via PATCH /api/v1/apps, turning on mandatory signature verification for every subsequent patch. Works for apps created before signing was enforced (grandfathered apps).

fcp codepush keys register
FlagRequiredDescription
--app-idNoApp ID to register the public key against. Defaults to the stored codepush_app_id from ~/.flutter_compilerc.
--public-keyNoPath to the PEM-encoded public key file. Defaults to ~/.flutter_codepush/codepush_public.pem.

Example

$ fcp codepush keys generate
⠋ Generating RSA-2048 keypair...
✓ Keypair generated
  Private key: ~/.flutter_codepush/codepush_private.pem
  Public key:  ~/.flutter_codepush/codepush_public.pem
  Stored path in ~/.flutter_compilerc

Next step: upload the public key to the server so it can
verify your patch signatures:

  fcp codepush keys register

$ fcp codepush keys register
⠋ Registering public key with https://api.codepush.flutterplaza.com
✓ Public key registered
  App ID:   app_k8x2m9f1
  Key path: ~/.flutter_codepush/codepush_public.pem

From now on, the server will verify the RSA-SHA256 signature
on every patch you upload for this app. Unsigned patches will
be rejected with HTTP 403.

fcp codepush setup

Download and cache the Code Push build tools for your current Flutter SDK version. Run this once per Flutter version before creating releases or patches.

fcp codepush setup

Flags

FlagRequiredDescription
--flutter-versionNoFlutter SDK version to set up (e.g. 3.41.2). Auto-detected from flutter --version if omitted.
--platformNoTarget platform (darwin-arm64, linux-x64, windows-x64, android-arm64, ios-arm64). Defaults to current host.
--forceNoRe-download even if the version is already cached.
--cleanupNoRemove cached build tools for other Flutter versions.
--list-versionsNoShow supported Flutter versions and exit.

Example

$ fcp codepush setup
✓ Flutter 3.41.2
✓ Flutter 3.41.2 is supported
✓ Build artifacts downloaded
  Cached at: ~/.flutter_compile/cache/codepush-engine/flutter-3.41.2
✓ Code push is ready for Flutter 3.41.2.

fcp codepush release

Create a new release by building your app and uploading the baseline. Run this from your Flutter project root.

fcp codepush release --build --platform apk --version <semver>

Flags

FlagRequiredDescription
--version, -vNoVersion for this release (e.g. 1.0.0+1). Defaults to the value in pubspec.yaml.
--buildNoBuild the app in release mode before uploading. Required unless --snapshot is provided.
--platform, -pNoTarget platform (apk, appbundle, ios, linux, macos, windows). Auto-detected if omitted.
--snapshotNoPath to a pre-built baseline file. Alternative to --build.
--app-idNoThe application ID. Falls back to the value stored by fcp codepush init.
--flutter-versionNoFlutter SDK version this release was built with. Auto-detected from flutter --version; required for server-side patch compilation.

Example

$ fcp codepush release --build --platform apk --version 1.2.0
✓ Ready
✓ Build succeeded
✓ Build finalized
✓ Release 1.2.0 uploaded
  Release ID:      rel_m1n2o3
  Version:         1.2.0
  Hash:            a1b2c3d4e5f6...
  Flutter version: 3.41.2

fcp codepush patch

Upload a patch for an existing release. With --build, the CLI builds your app, diffs it against the baseline, signs the packaged patch bytes with your RSA key, and uploads. The signature is sent to the server in the signature field of the POST body and verified against the public key registered for your app (see Patch Signing & Migration).

Grandfathered apps: If you created your app with a CLI older than 0.19.8, the server has no public key on file for it. Unsigned patches are still accepted, but every upload prints a loud migration banner telling you to run fcp codepush keys register. Future releases will eventually remove this grandfather path — register now and avoid the rush.
fcp codepush patch --build --platform apk --release-id <id>

Flags

FlagRequiredDescription
--release-idYesThe release ID to patch against.
--buildNoBuild the app and produce a patch file automatically.
--platformNoTarget platform (apk, appbundle, ios, linux, macos, windows). Required with --build.
--patch-fileNoPath to a patch file produced by a previous --build run. Alternative to rebuilding.
--baselineNoPath to the baseline file for binary diffing (smaller patches).
--rolloutNoPercentage of devices to receive the patch (1–100). Defaults to 100.
--channelNoDeployment channel (beta or production). Defaults to production.
--signing-keyNoPath to RSA private key. Auto-detected from config if omitted.
--unsignedNoAllow uploading an unsigned patch (testing only — never use in production).

Example

$ fcp codepush patch --build --platform apk --release-id rel_m1n2o3 --rollout 25
✓ Ready
✓ Build succeeded
✓ Build finalized
✓ Diff: 49152 bytes (saved 2416832 bytes)
✓ Signed (256 bytes)
✓ Patch ready → build/codepush/patch.fcppatch
✓ Patch uploaded
  Patch ID:     p_x1y2z3
  Patch #:      4
  Rollout:      25%
  Channel:      production

fcp codepush rollback

Rollback a previously published patch by ID. Devices will revert to the baseline release (or to the previous still-active patch) on their next update check.

fcp codepush rollback --patch-id <patch-id>

Flags

FlagRequiredDescription
--patch-idYesThe patch ID to roll back. Find it in the output of fcp codepush patch or fcp codepush status --json.

Example

$ fcp codepush rollback --patch-id p_x1y2z3
✓ Patch p_x1y2z3 rolled back
  Devices will revert on the next update check.

fcp codepush status

Show current releases and patches for an app, including rollout percentage and install counts.

fcp codepush status --app-id <id>

Flags

FlagRequiredDescription
--app-idNoThe application ID. Falls back to the value stored by fcp codepush init.
--release-idNoIf set (with --json), output only the patches for this release.
--jsonNoEmit machine-readable JSON instead of a formatted table.

Example

$ fcp codepush status --app-id app_7x8y9z
Release 1.2.0
  ID:      rel_m1n2o3
  Created: 2026-03-09T14:22:00Z
  Patches:
    p_x1y2z3  #4  rollout=100%  channel=production  active

Framework Integration

Integrate Code Push into your Flutter application to receive over-the-air updates at runtime.

The CodePush Widget

The easiest way to add Code Push to your app is by wrapping your root widget with the CodePush widget. It handles update checking, downloading, and applying patches automatically.

import 'package:flutter/material.dart';
import 'package:flutterplaza_code_push/flutterplaza_code_push.dart';

void main() {
  runApp(
    CodePush(
      appId: 'app_7x8y9z',
      releaseVersion: '1.2.0',
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: const HomeScreen(),
    );
  }
}

CodePush Widget Parameters

ParameterTypeRequiredDescription
appIdStringYesYour application ID from the Code Push console.
releaseVersionStringYesThe release version this build was compiled from.
childWidgetYesYour root application widget.
checkOnResumeboolNoCheck for updates when the app resumes from background. Defaults to true.
showUpdateDialogboolNoShow a dialog when an update is downloaded. Defaults to false.
onUpdateAvailableVoidCallback?NoCalled when a new patch is available.
onUpdateAppliedVoidCallback?NoCalled after a patch has been applied.

Checking for Updates Manually

If you need finer control over when updates are checked and applied, use the CodePushClient directly:

import 'package:flutterplaza_code_push/flutterplaza_code_push.dart';

final client = CodePushClient(
  appId: 'app_7x8y9z',
  releaseVersion: '1.2.0',
);

Future<void> checkForUpdates() async {
  final updateInfo = await client.checkForUpdate();

  if (updateInfo.updateAvailable) {
    print('Patch ${updateInfo.patchNumber} available '
          '(${updateInfo.patchSize} bytes)');

    // Download and apply the patch
    await client.downloadAndApply(updateInfo);

    print('Patch applied! Restart to take effect.');
  } else {
    print('App is up to date.');
  }
}

// Check update status
Future<void> getStatus() async {
  final status = client.currentPatchStatus;
  print('Current patch: ${status.patchNumber ?? "none"}');
  print('Last check: ${status.lastCheckTime}');
}

Platform-Specific Setup

Android

Add the following to your android/app/build.gradle:

android {
    // ... existing configuration

    defaultConfig {
        // Ensure the Code Push native library is included
        ndk {
            abiFilters 'arm64-v8a', 'x86_64'
        }
    }
}

Add internet permission to android/app/src/main/AndroidManifest.xml (if not already present):

<uses-permission android:name="android.permission.INTERNET" />

iOS

iOS configuration is handled automatically by fcp codepush init, which writes your public signing key into Info.plist so the runtime can verify incoming patches.

If you have strict App Transport Security (ATS) rules, ensure the Code Push server domain is allowed in your Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>
App Store compliance: Over-the-air updates for interpreted code are permitted under Apple's App Store Review Guidelines (section 3.3.2), provided updates do not change the app's primary purpose. Code Push only updates Dart code and never downloads or executes arbitrary native machine code.

Testing Locally

You can test the full Code Push flow locally before pushing to production:

  1. Create a dev release:
    fcp codepush release --build --platform apk --version 0.0.1-dev

    Note the release ID printed in the output — you'll need it in the next step.

  2. Run your app on a device or emulator. The app will check for updates on launch.
  3. Make a code change and push a patch against the release ID:
    fcp codepush patch --build --platform apk --release-id <release-id>
  4. Restart the app (or trigger an update check). You should see the patch applied.
Tip: Use fcp codepush status to verify your patch was uploaded and is active before restarting the app.

Telemetry

Enable device telemetry to see crash reports, success rates, and boot metrics in the Console. Telemetry is opt-in — your app only sends reports when you enable it.

Enable telemetry

Add telemetry_enabled: true to your codepush.yaml:

# codepush.yaml
app_id: your_app_id
server_url: https://api.codepush.flutterplaza.com
telemetry_enabled: true

What data is sent?

When enabled, the Code Push client sends a lightweight report on each app launch:

FieldDescription
app_idYour app identifier
patch_idActive patch ID (if any)
successWhether the launch succeeded
boot_countNumber of consecutive boots (for crash detection)
platformandroid, ios, macos, linux, or windows
error_messageError description (if launch failed)

No user data, no device identifiers, no source code, and no network traffic when disabled.

View telemetry

In the Console, go to your app's detail page. The Telemetry section shows:

  • Success rate across all devices
  • Total reports and unique devices
  • Per-patch success/failure breakdown
  • Recent failures with error messages
Why enable telemetry? It lets you know immediately if a patch is causing crashes on your users' devices — before they report it. Combined with automatic rollback, this gives you confidence to push updates faster.

Patch Signing & Migration

How FlutterPlaza Code Push uses RSA‑SHA256 signatures to guarantee that every patch applied to a user’s device was built by you, not intercepted or tampered with in transit — and how to migrate an existing project to the new enforcement model.

Short version: If you’re on flutter_compile 0.19.8 or later and ran fcp codepush init with that CLI, you’re already signed. If you upgraded from an older CLI, run fcp codepush keys register once and you’re done. The grandfather path prints a banner on every unsigned upload until you do.

How It Works

Signing happens entirely on your machine — the server never sees your private key:

  1. fcp codepush init (or fcp codepush keys generate) creates an RSA‑2048 keypair under ~/.flutter_codepush/: codepush_private.pem and codepush_public.pem. The private-key path is also stored in ~/.flutter_compilerc so later commands can find it.
  2. Either the same init call or a follow-up fcp codepush keys register uploads the public key to the server, where it’s attached to your app record.
  3. Every fcp codepush patch run reads the packaged .fcppatch bytes from disk, signs them with the private key using RSA-SHA256, and sends the base64-encoded signature alongside the patch in the POST body.
  4. The server receives patch + signature, looks up the app’s stored public key, and performs RSA‑SHA256 verification. If and only if verification succeeds, the patch is accepted and made available to devices.

The signature covers the exact bytes that devices eventually download, so any tampering — by a compromised CI runner, a man-in-the-middle on the wire, or anyone with a stolen API token — is detected before the patch can reach a user.

What the server enforces

App stateUnsigned patchSigned patch
Public key registered (any new app from CLI 0.19.8+, or any app after fcp codepush keys register) Rejected with HTTP 403 signature required. Verified against the stored public key. Accepted only on success.
No public key registered (grandfathered: app was created before 0.19.8 and has not been migrated yet) Accepted with a signature_enforcement block in the response. A warning is logged server-side and the CLI prints a loud migration banner. Accepted. Still carries the signature for future validation.
Grandfathering will eventually be removed. We keep it today so that existing deployments don’t break on upgrade. A future release will flip the server to reject unsigned patches unconditionally — if your app has no public key on file by then, your next patch upload will fail. Run fcp codepush keys register now to stay ahead of it.

Enabling Signature Verification

Pick the path that matches your current state:

Brand new project

Just run init. The 0.19.8+ CLI handles everything in a single command: keygen, app create, and public-key registration.

dart pub global activate flutter_compile
fcp codepush login
fcp codepush init
# done — signature enforcement is on from your first patch.

Existing project, pre-0.19.8 CLI

Upgrade the CLI, then run keys generate + keys register once. Your app ID, server URL, and token are preserved; only the signing key is added.

dart pub global activate flutter_compile
fcp --version                   # should print 0.19.8 or later

fcp codepush keys generate      # creates ~/.flutter_codepush/codepush_{private,public}.pem
fcp codepush keys register      # PATCH /api/v1/apps with the public key

fcp codepush patch --build --platform ios --release-id <your-release-id>
# ^ signed, server-verified, no migration banner.

I already have a key — just not on the server

If ~/.flutter_codepush/codepush_public.pem exists (because a 0.15.0–0.19.7 CLI generated it during init), skip generate and go straight to register:

fcp codepush keys register

Rotating a key

If you need to rotate (key compromise, team handover, lost laptop), regenerate with --force and re-register. The new key overwrites the stored public key on the server — any patch signed with the old key will be rejected from that point on.

fcp codepush keys generate --force
fcp codepush keys register

Migration by CLI Version

Use this table to find the exact commands you need based on the CLI version your project was originally initialised with. If you’ve used multiple versions over time, pick the earliest one you remember running fcp codepush init from.

CLI at init Local key? Server key? What to run
< 0.15.0 ❌ init didn’t generate one fcp codepush keys generate
fcp codepush keys register
0.15.0 – 0.19.7 ✅ generated by init, stored in ~/.flutter_codepush/ ❌ server never saw it fcp codepush keys register
0.19.8+ (fresh init) ✅ uploaded in the init call nothing — signature enforcement is already on.
0.19.8+ on a pre-0.19.8 project Depends on original init ❌ server still has no key Follow the pre-0.15.0 or 0.15.0–0.19.7 row above. Upgrading the CLI alone does not migrate an existing app.

Verifying the migration

After running the commands above, push a patch. On a successfully-migrated app you should see no signature_enforcement block in the output — just the usual patch-uploaded summary. If you still see a migration banner, the server doesn’t have your key yet; re-run fcp codepush keys register and check the CLI output for a non-2xx status.

Doing nothing (for now)

Grandfathered apps keep working — unsigned patches are accepted, and your users aren’t affected. The cost is:

  • Every patch upload prints a loud migration banner on the CLI.
  • Every unsigned upload is logged server-side, so you’ll see the backlog grow on the admin dashboard.
  • When grandfathering is removed (no hard date yet, but expect it within a few minor releases), your next unsigned upload will fail with HTTP 403 signature required and nothing will ship until you migrate.

The migration is < 60 seconds of work. Do it now.

Common Signing Errors

Error: No signing key found. Patches must be signed for production.

Cause: Neither --signing-key was passed nor is codepush_signing_key in ~/.flutter_compilerc.

Fix:

fcp codepush keys generate

This writes the keypair and stores the private-key path in the rc file. Subsequent patch runs pick it up automatically.

Error (HTTP 403): signature required — This app has a registered public key and only accepts signed patches.

Cause: The server has a public key on file for your app, but your patch upload didn’t include a signature field. Almost always means you ran fcp codepush patch --unsigned on a signed-enforcement app.

Fix: Drop the --unsigned flag. If you no longer have the private key, regenerate and rotate:

fcp codepush keys generate --force
fcp codepush keys register
Error (HTTP 403): signature verification failed — The signature did not verify against the public key registered for this app.

Cause: Your local private key and the public key stored on the server don’t match. This happens after a key rotation where generate --force was run but register was not, or after restoring a machine from a backup that’s out of sync with your server record.

Fix: Push the current public key to the server:

fcp codepush keys register

If you’re not sure which key is current on the server, regenerate and rotate to be safe (generate --force + register). All previously-signed patches remain readable by devices — verification happens at upload time, not at device runtime.

Error: Could not generate signing keys (openssl missing?)

Cause: fcp codepush keys generate and fcp codepush init shell out to openssl genrsa / openssl rsa. If openssl isn’t on PATH, both fail.

Fix: Install openssl and re-run:

# macOS (Homebrew)
brew install openssl

# Debian / Ubuntu
sudo apt-get install openssl

# Verify
openssl version

# Then retry
fcp codepush keys generate

Troubleshooting

Solutions for common issues you may encounter when using FlutterPlaza Code Push.

Build Tools Not Found

Error: Build tool not available. Run "fcp codepush setup" first.

Cause: The Code Push build tools have not been downloaded for your current Flutter SDK version, or you recently upgraded Flutter without re-running setup.

Fix:

# Download build tools for your current Flutter version
fcp codepush setup

# Force re-download if the cache seems corrupted
fcp codepush setup --force

If the error persists, verify your Flutter version and check that your internet connection can reach the artifact server:

flutter --version
curl -I https://api.codepush.flutterplaza.com/api/v1/health

Patch Failed to Apply

Error: Patch verification failed: snapshot hash mismatch

Cause: The patch was built against a different baseline than what is installed on the device. This usually happens when:

  • The release was rebuilt without updating the version number.
  • The device is running a different release version than the patch targets.
  • The Flutter SDK version changed between the release and patch builds.

Fix:

  1. Ensure the device is running the exact release version the patch was built against.
  2. Verify the Flutter SDK version matches:
    flutter --version
    fcp codepush status --app-id <app-id>
  3. If versions do not match, create a new release with the current SDK version and rebuild the patch.

Rate Limit Exceeded

Error: 429 Too Many Requests: Rate limit exceeded. Try again in 60 seconds.

Cause: You have exceeded the API rate limits for your current plan. Rate limits are applied per-account and reset on a rolling window.

Fix:

  • Wait and retry: The error message includes a retry delay. Wait the indicated time and try again.
  • Upgrade your plan: Higher-tier plans have more generous rate limits. Check your current usage and limits:
    fcp codepush billing usage
  • Optimize your workflow: Avoid pushing multiple patches in rapid succession. Batch your changes into a single patch.

Need More Help?

If your issue is not listed here:

  • Run your command with --verbose to get detailed debug output.
  • Check the GitHub Issues for known bugs and workarounds.
  • Open a new issue with the verbose output, your Flutter version, and the steps to reproduce.