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.
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:
| Requirement | Minimum Version | Check Command |
|---|---|---|
| Flutter SDK | 3.22.0 | flutter --version |
| Dart SDK | 3.4.0 | dart --version |
| fcp CLI | latest | fcp --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
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.
CLI Reference
Complete reference for every fcp command, including usage, flags, and examples.
--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
| Flag | Required | Description |
|---|---|---|
--api-key | No | Skip the browser flow and authenticate with an API key directly. |
--server | No | Server 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
| Flag | Required | Description |
|---|---|---|
--app-id | No | Use 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.
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
| Flag | Required | Description |
|---|---|---|
--output-dir | No | Directory to write the keypair into. Defaults to ~/.flutter_codepush/. |
--force | No | Overwrite 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
| Flag | Required | Description |
|---|---|---|
--app-id | No | App ID to register the public key against. Defaults to the stored codepush_app_id from ~/.flutter_compilerc. |
--public-key | No | Path 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
| Flag | Required | Description |
|---|---|---|
--flutter-version | No | Flutter SDK version to set up (e.g. 3.41.2). Auto-detected from flutter --version if omitted. |
--platform | No | Target platform (darwin-arm64, linux-x64, windows-x64, android-arm64, ios-arm64). Defaults to current host. |
--force | No | Re-download even if the version is already cached. |
--cleanup | No | Remove cached build tools for other Flutter versions. |
--list-versions | No | Show 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
| Flag | Required | Description |
|---|---|---|
--version, -v | No | Version for this release (e.g. 1.0.0+1). Defaults to the value in pubspec.yaml. |
--build | No | Build the app in release mode before uploading. Required unless --snapshot is provided. |
--platform, -p | No | Target platform (apk, appbundle, ios, linux, macos, windows). Auto-detected if omitted. |
--snapshot | No | Path to a pre-built baseline file. Alternative to --build. |
--app-id | No | The application ID. Falls back to the value stored by fcp codepush init. |
--flutter-version | No | Flutter 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).
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
| Flag | Required | Description |
|---|---|---|
--release-id | Yes | The release ID to patch against. |
--build | No | Build the app and produce a patch file automatically. |
--platform | No | Target platform (apk, appbundle, ios, linux, macos, windows). Required with --build. |
--patch-file | No | Path to a patch file produced by a previous --build run. Alternative to rebuilding. |
--baseline | No | Path to the baseline file for binary diffing (smaller patches). |
--rollout | No | Percentage of devices to receive the patch (1–100). Defaults to 100. |
--channel | No | Deployment channel (beta or production). Defaults to production. |
--signing-key | No | Path to RSA private key. Auto-detected from config if omitted. |
--unsigned | No | Allow 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
| Flag | Required | Description |
|---|---|---|
--patch-id | Yes | The 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
| Flag | Required | Description |
|---|---|---|
--app-id | No | The application ID. Falls back to the value stored by fcp codepush init. |
--release-id | No | If set (with --json), output only the patches for this release. |
--json | No | Emit 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
| Parameter | Type | Required | Description |
|---|---|---|---|
appId | String | Yes | Your application ID from the Code Push console. |
releaseVersion | String | Yes | The release version this build was compiled from. |
child | Widget | Yes | Your root application widget. |
checkOnResume | bool | No | Check for updates when the app resumes from background. Defaults to true. |
showUpdateDialog | bool | No | Show a dialog when an update is downloaded. Defaults to false. |
onUpdateAvailable | VoidCallback? | No | Called when a new patch is available. |
onUpdateApplied | VoidCallback? | No | Called 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>
Testing Locally
You can test the full Code Push flow locally before pushing to production:
-
Create a dev release:
fcp codepush release --build --platform apk --version 0.0.1-devNote the release ID printed in the output — you'll need it in the next step.
- Run your app on a device or emulator. The app will check for updates on launch.
-
Make a code change and push a patch against the release ID:
fcp codepush patch --build --platform apk --release-id <release-id> - Restart the app (or trigger an update check). You should see the patch applied.
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:
| Field | Description |
|---|---|
app_id | Your app identifier |
patch_id | Active patch ID (if any) |
success | Whether the launch succeeded |
boot_count | Number of consecutive boots (for crash detection) |
platform | android, ios, macos, linux, or windows |
error_message | Error 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
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.
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:
fcp codepush init(orfcp codepush keys generate) creates an RSA‑2048 keypair under~/.flutter_codepush/:codepush_private.pemandcodepush_public.pem. The private-key path is also stored in~/.flutter_compilercso later commands can find it.- Either the same
initcall or a follow-upfcp codepush keys registeruploads the public key to the server, where it’s attached to your app record. - Every
fcp codepush patchrun reads the packaged.fcppatchbytes from disk, signs them with the private key usingRSA-SHA256, and sends the base64-encoded signature alongside the patch in the POST body. - 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 state | Unsigned patch | Signed 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. |
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 generatefcp 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 requiredand nothing will ship until you migrate.
The migration is < 60 seconds of work. Do it now.
Common Signing Errors
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.
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
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.
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
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
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:
- Ensure the device is running the exact release version the patch was built against.
- Verify the Flutter SDK version matches:
flutter --version fcp codepush status --app-id <app-id> - If versions do not match, create a new release with the current SDK version and rebuild the patch.
Rate Limit Exceeded
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
--verboseto 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.