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 engine artifacts
Code Push requires custom engine artifacts that match your Flutter SDK version. The setup command auto-detects your version and downloads the correct binaries.
fcp codepush setup
2. Register an account
fcp auth register --email you@example.com
You will receive a confirmation email with your API key. The CLI stores the key locally so you only need to register once per machine.
3. Create an app
fcp apps create --name "My App" --platform android
This returns an app ID that you will use in subsequent commands. Supported platforms: android, ios.
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 --app-id <app-id> --version 1.0.0
This compiles your Dart code, takes a snapshot, and uploads it to the Code Push server.
5. Push a patch
After making changes to your Dart code, push a patch against the release:
fcp codepush patch --app-id <app-id> --release 1.0.0
The CLI computes a diff between your current code and the baseline, uploads the patch, and makes it available to all devices running version 1.0.0.
CLI Reference
Complete reference for every fcp command, including usage, flags, and examples.
--verbose for detailed output and --help for usage information.
fcp auth register
Register a new FlutterPlaza Code Push account.
fcp auth register --email <email>
Flags
| Flag | Required | Description |
|---|---|---|
--email | Yes | Your email address. A confirmation email with your API key will be sent here. |
Example
$ fcp auth register --email dev@mycompany.com
Registering account for dev@mycompany.com...
Success! Check your email for the API key.
API key saved to ~/.config/fcp/credentials.json
fcp auth login
Authenticate with an existing API key. Use this on a new machine or after credentials have been cleared.
fcp auth login --api-key <key>
Flags
| Flag | Required | Description |
|---|---|---|
--api-key | Yes | Your API key (received via email during registration). |
Example
$ fcp auth login --api-key cpk_a1b2c3d4e5f6
Logged in successfully.
Credentials saved to ~/.config/fcp/credentials.json
fcp apps create
Create a new application in your Code Push account.
fcp apps create --name <name> --platform <platform>
Flags
| Flag | Required | Description |
|---|---|---|
--name | Yes | Display name for the app. |
--platform | Yes | Target platform: android or ios. |
Example
$ fcp apps create --name "My Shopping App" --platform android
App created successfully.
App ID: app_7x8y9z
Name: My Shopping App
Platform: android
fcp apps list
List all applications in your account.
fcp apps list
Flags
| Flag | Required | Description |
|---|---|---|
--json | No | Output raw JSON instead of a formatted table. |
Example
$ fcp apps list
ID NAME PLATFORM CREATED
app_7x8y9z My Shopping App android 2026-03-08
app_3a4b5c Weather Tracker ios 2026-03-01
fcp codepush setup
Download and install the Code Push engine artifacts for your current Flutter SDK version. This must be run before you can create releases or patches.
fcp codepush setup
Flags
| Flag | Required | Description |
|---|---|---|
--flutter-root | No | Path to Flutter SDK. Auto-detected by default. |
--force | No | Re-download even if artifacts already exist. |
Example
$ fcp codepush setup
Detecting Flutter version... 3.22.2
Downloading engine artifacts for 3.22.2...
android-arm64 [========================================] 100%
android-x64 [========================================] 100%
Engine artifacts installed successfully.
fcp codepush release
Create a new release by compiling your app and uploading the baseline snapshot. Run this from your Flutter project root.
fcp codepush release --app-id <id> --version <semver>
Flags
| Flag | Required | Description |
|---|---|---|
--app-id | Yes | The application ID (from fcp apps create). |
--version | Yes | Semantic version for this release (e.g., 1.0.0). |
--target | No | Entry point file. Defaults to lib/main.dart. |
--flavor | No | Build flavor (e.g., production, staging). |
Example
$ fcp codepush release --app-id app_7x8y9z --version 1.2.0
Compiling release build...
Generating baseline snapshot...
Uploading snapshot (2.4 MB)...
Release 1.2.0 created successfully.
Release ID: rel_m1n2o3
Snapshot: sha256:a1b2c3...
fcp codepush patch
Upload a patch for an existing release. The CLI compiles your current code, diffs it against the baseline, and uploads the resulting patch.
fcp codepush patch --app-id <id> --release <version>
Flags
| Flag | Required | Description |
|---|---|---|
--app-id | Yes | The application ID. |
--release | Yes | The release version to patch against. |
--target | No | Entry point file. Defaults to lib/main.dart. |
--message | No | Description of what changed in this patch. |
--rollout | No | Percentage of users to receive the patch (1-100). Defaults to 100. |
Example
$ fcp codepush patch --app-id app_7x8y9z --release 1.2.0 \
--message "Fix checkout button alignment"
Compiling patched build...
Computing diff against release 1.2.0 baseline...
Patch size: 48 KB (98% smaller than full snapshot)
Uploading patch...
Patch p_x1y2z3 published for release 1.2.0.
Rollout: 100% of devices
fcp codepush rollback
Rollback a previously published patch so devices revert to the baseline release (or to the previous patch).
fcp codepush rollback --app-id <id> --release <version>
Flags
| Flag | Required | Description |
|---|---|---|
--app-id | Yes | The application ID. |
--release | Yes | The release version to rollback the active patch for. |
--patch-id | No | Specific patch ID to rollback. Defaults to the latest patch. |
Example
$ fcp codepush rollback --app-id app_7x8y9z --release 1.2.0
Rolling back active patch for release 1.2.0...
Patch p_x1y2z3 has been rolled back.
Devices will revert to the baseline on next update check.
fcp codepush status
Show the current patch status for a release, including active patch, rollout percentage, and install metrics.
fcp codepush status --app-id <id> --release <version>
Flags
| Flag | Required | Description |
|---|---|---|
--app-id | Yes | The application ID. |
--release | Yes | The release version to check. |
Example
$ fcp codepush status --app-id app_7x8y9z --release 1.2.0
Release 1.2.0 (rel_m1n2o3)
Active Patch: p_x1y2z3
Patch Message: Fix checkout button alignment
Rollout: 100%
Installs: 1,247 / 3,891 devices (32%)
Published: 2026-03-09 14:22 UTC
API Reference
The FlutterPlaza Code Push REST API. Base URL: https://api.codepush.flutterplaza.com
Authentication
The API uses a two-layer authentication model:
- API Key — Passed in the
x-api-keyheader. Used for initial authentication and to obtain a JWT. - JWT Bearer Token — Passed in the
Authorization: Bearer <token>header. Obtained from the login endpoint. Expires after 24 hours.
Most endpoints require the JWT bearer token. The register and login endpoints require only the API key (or no auth at all, as noted).
# Example: authenticated request
curl -X GET \
https://api.codepush.flutterplaza.com/api/v1/apps \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json"
All request and response bodies use JSON. Errors return a consistent shape:
{
"error": {
"code": "INVALID_REQUEST",
"message": "The 'name' field is required."
}
}
POST /api/v1/auth/register
PublicRegister a new account and receive an API key.
Request Body
{
"email": "dev@mycompany.com"
}
Response 201
{
"id": "usr_a1b2c3",
"email": "dev@mycompany.com",
"api_key": "cpk_a1b2c3d4e5f6g7h8",
"created_at": "2026-03-10T12:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_EMAIL | The email address is not valid. |
| 409 | EMAIL_EXISTS | An account with this email already exists. |
POST /api/v1/auth/login
PublicExchange an API key for a JWT bearer token.
Request Body
{
"api_key": "cpk_a1b2c3d4e5f6g7h8"
}
Response 200
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2026-03-11T12:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 401 | INVALID_API_KEY | The API key is invalid or has been revoked. |
GET /api/v1/account
AuthenticatedRetrieve the current account details, including usage and plan information.
Response 200
{
"id": "usr_a1b2c3",
"email": "dev@mycompany.com",
"plan": "free",
"apps_count": 2,
"patches_this_month": 14,
"created_at": "2026-03-01T10:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or expired bearer token. |
GET /api/v1/apps
AuthenticatedList all applications belonging to the authenticated account.
Response 200
{
"apps": [
{
"id": "app_7x8y9z",
"name": "My Shopping App",
"platform": "android",
"created_at": "2026-03-08T09:00:00Z"
},
{
"id": "app_3a4b5c",
"name": "Weather Tracker",
"platform": "ios",
"created_at": "2026-03-01T15:30:00Z"
}
]
}
POST /api/v1/apps
AuthenticatedCreate a new application.
Request Body
{
"name": "My Shopping App",
"platform": "android"
}
Response 201
{
"id": "app_7x8y9z",
"name": "My Shopping App",
"platform": "android",
"created_at": "2026-03-10T12:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Missing required fields (name, platform). |
| 400 | INVALID_PLATFORM | Platform must be android or ios. |
| 403 | APP_LIMIT_REACHED | You have reached the maximum number of apps for your plan. |
GET /api/v1/releases
AuthenticatedList releases for an application.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
app_id | Yes | The application ID. |
Response 200
{
"releases": [
{
"id": "rel_m1n2o3",
"app_id": "app_7x8y9z",
"version": "1.2.0",
"snapshot_hash": "sha256:a1b2c3d4...",
"snapshot_size": 2457600,
"created_at": "2026-03-09T10:00:00Z"
}
]
}
POST /api/v1/releases
AuthenticatedCreate a new release by uploading a baseline snapshot. Send the request as multipart/form-data.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
app_id | string | Yes | The application ID. |
version | string | Yes | Semantic version (e.g., 1.2.0). |
snapshot | file | Yes | The compiled baseline snapshot file. |
flutter_version | string | Yes | Flutter SDK version used to compile. |
Response 201
{
"id": "rel_m1n2o3",
"app_id": "app_7x8y9z",
"version": "1.2.0",
"snapshot_hash": "sha256:a1b2c3d4...",
"snapshot_size": 2457600,
"flutter_version": "3.22.2",
"created_at": "2026-03-10T12:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_VERSION | Version string is not valid semver. |
| 409 | VERSION_EXISTS | A release with this version already exists for this app. |
| 413 | SNAPSHOT_TOO_LARGE | Snapshot exceeds the maximum allowed size (50 MB). |
GET /api/v1/patches
AuthenticatedList patches for a specific release.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
app_id | Yes | The application ID. |
release_id | Yes | The release ID. |
Response 200
{
"patches": [
{
"id": "p_x1y2z3",
"release_id": "rel_m1n2o3",
"number": 1,
"message": "Fix checkout button alignment",
"size": 49152,
"rollout_percentage": 100,
"is_rolled_back": false,
"installs": 1247,
"created_at": "2026-03-09T14:22:00Z"
}
]
}
POST /api/v1/patches
AuthenticatedUpload a new patch for a release. Send as multipart/form-data.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
app_id | string | Yes | The application ID. |
release_id | string | Yes | The release ID to patch against. |
patch | file | Yes | The binary diff file. |
message | string | No | Description of the patch. |
rollout_percentage | integer | No | Percentage of users (1-100). Defaults to 100. |
Response 201
{
"id": "p_x1y2z3",
"release_id": "rel_m1n2o3",
"number": 1,
"message": "Fix checkout button alignment",
"size": 49152,
"rollout_percentage": 100,
"hash": "sha256:f1e2d3c4...",
"created_at": "2026-03-10T14:22:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Missing required fields. |
| 404 | RELEASE_NOT_FOUND | The specified release does not exist. |
| 413 | PATCH_TOO_LARGE | Patch exceeds the maximum allowed size (10 MB). |
| 429 | RATE_LIMITED | Too many patches in a short period. Wait and retry. |
POST /api/v1/patches/rollback
AuthenticatedRollback a patch so that devices revert to the baseline (or previous patch).
Request Body
{
"app_id": "app_7x8y9z",
"release_id": "rel_m1n2o3",
"patch_id": "p_x1y2z3"
}
Response 200
{
"id": "p_x1y2z3",
"is_rolled_back": true,
"rolled_back_at": "2026-03-10T16:00:00Z"
}
Errors
| Status | Code | Description |
|---|---|---|
| 404 | PATCH_NOT_FOUND | The specified patch does not exist. |
| 409 | ALREADY_ROLLED_BACK | This patch has already been rolled back. |
GET /api/v1/updates
Public (Device)Check for available updates. This endpoint is called by the Code Push client library running on the device. No user authentication is required; the app ID and release version identify the context.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
app_id | Yes | The application ID. |
release_version | Yes | The release version currently installed on the device. |
patch_number | No | The current patch number installed (if any). |
platform | Yes | Device platform (android or ios). |
arch | Yes | Device architecture (arm64, x64). |
Response 200 (update available)
{
"update_available": true,
"patch": {
"number": 2,
"download_url": "https://storage.googleapis.com/...",
"hash": "sha256:f1e2d3c4...",
"size": 49152
}
}
Response 200 (no update)
{
"update_available": false
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Missing required query parameters. |
| 404 | APP_NOT_FOUND | No app matches the given ID. |
Billing Endpoints
Endpoints for managing billing, viewing usage, and checking pricing tiers.
GET /api/v1/billing/pricing
PublicRetrieve available pricing plans.
Response 200
{
"plans": [
{
"id": "free",
"name": "Free",
"price_monthly": 0,
"max_apps": 2,
"max_patches_per_month": 50,
"max_snapshot_size_mb": 10
},
{
"id": "pro",
"name": "Pro",
"price_monthly": 29,
"max_apps": 20,
"max_patches_per_month": 500,
"max_snapshot_size_mb": 50
},
{
"id": "team",
"name": "Team",
"price_monthly": 99,
"max_apps": -1,
"max_patches_per_month": -1,
"max_snapshot_size_mb": 100
}
]
}
GET /api/v1/billing/usage
AuthenticatedGet current billing period usage for the authenticated account.
Response 200
{
"plan": "free",
"billing_period_start": "2026-03-01T00:00:00Z",
"billing_period_end": "2026-03-31T23:59:59Z",
"apps_used": 1,
"apps_limit": 2,
"patches_used": 14,
"patches_limit": 50,
"storage_used_mb": 4.2,
"storage_limit_mb": 100
}
POST /api/v1/billing/checkout
AuthenticatedStart a checkout session to upgrade or change your plan. Returns a URL to complete payment.
Request Body
{
"plan_id": "pro"
}
Response 200
{
"checkout_url": "https://checkout.stripe.com/c/pay/...",
"session_id": "cs_live_a1b2c3..."
}
Errors
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_PLAN | The specified plan does not exist. |
| 409 | ALREADY_SUBSCRIBED | You are already on this plan. |
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:flutter_code_push/flutter_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:flutter_code_push/flutter_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
No additional configuration is required for iOS. The Code Push library integrates automatically through the Flutter plugin system.
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 debug release:
fcp codepush release --app-id app_7x8y9z --version 0.0.1-dev - Run your app on a device or emulator. The app will check for updates on launch.
-
Make a code change and push a patch:
fcp codepush patch --app-id app_7x8y9z --release 0.0.1-dev \ --message "Test patch" - 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.
Troubleshooting
Solutions for common issues you may encounter when using FlutterPlaza Code Push.
Engine Artifacts Not Found
Engine artifacts not found for Flutter 3.22.2. Run 'fcp codepush setup' first.
Cause: The Code Push engine artifacts have not been downloaded for your current Flutter SDK version, or you recently upgraded Flutter without re-running setup.
Fix:
# Download artifacts for your current Flutter version
fcp codepush setup
# Force re-download if artifacts seem 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> --release <version> - 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 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.