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 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
Note: You must re-run this command each time you upgrade your Flutter SDK version.

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.

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 auth register

Register a new FlutterPlaza Code Push account.

fcp auth register --email <email>

Flags

FlagRequiredDescription
--emailYesYour 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

FlagRequiredDescription
--api-keyYesYour 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

FlagRequiredDescription
--nameYesDisplay name for the app.
--platformYesTarget 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

FlagRequiredDescription
--jsonNoOutput 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

FlagRequiredDescription
--flutter-rootNoPath to Flutter SDK. Auto-detected by default.
--forceNoRe-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

FlagRequiredDescription
--app-idYesThe application ID (from fcp apps create).
--versionYesSemantic version for this release (e.g., 1.0.0).
--targetNoEntry point file. Defaults to lib/main.dart.
--flavorNoBuild 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

FlagRequiredDescription
--app-idYesThe application ID.
--releaseYesThe release version to patch against.
--targetNoEntry point file. Defaults to lib/main.dart.
--messageNoDescription of what changed in this patch.
--rolloutNoPercentage 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

FlagRequiredDescription
--app-idYesThe application ID.
--releaseYesThe release version to rollback the active patch for.
--patch-idNoSpecific 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

FlagRequiredDescription
--app-idYesThe application ID.
--releaseYesThe 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:

  1. API Key — Passed in the x-api-key header. Used for initial authentication and to obtain a JWT.
  2. 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

Public

Register 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

StatusCodeDescription
400INVALID_EMAILThe email address is not valid.
409EMAIL_EXISTSAn account with this email already exists.

POST /api/v1/auth/login

Public

Exchange 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

StatusCodeDescription
401INVALID_API_KEYThe API key is invalid or has been revoked.

GET /api/v1/account

Authenticated

Retrieve 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

StatusCodeDescription
401UNAUTHORIZEDMissing or expired bearer token.

GET /api/v1/apps

Authenticated

List 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

Authenticated

Create 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

StatusCodeDescription
400INVALID_REQUESTMissing required fields (name, platform).
400INVALID_PLATFORMPlatform must be android or ios.
403APP_LIMIT_REACHEDYou have reached the maximum number of apps for your plan.

GET /api/v1/releases

Authenticated

List releases for an application.

Query Parameters

ParameterRequiredDescription
app_idYesThe 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

Authenticated

Create a new release by uploading a baseline snapshot. Send the request as multipart/form-data.

Form Fields

FieldTypeRequiredDescription
app_idstringYesThe application ID.
versionstringYesSemantic version (e.g., 1.2.0).
snapshotfileYesThe compiled baseline snapshot file.
flutter_versionstringYesFlutter 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

StatusCodeDescription
400INVALID_VERSIONVersion string is not valid semver.
409VERSION_EXISTSA release with this version already exists for this app.
413SNAPSHOT_TOO_LARGESnapshot exceeds the maximum allowed size (50 MB).

GET /api/v1/patches

Authenticated

List patches for a specific release.

Query Parameters

ParameterRequiredDescription
app_idYesThe application ID.
release_idYesThe 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

Authenticated

Upload a new patch for a release. Send as multipart/form-data.

Form Fields

FieldTypeRequiredDescription
app_idstringYesThe application ID.
release_idstringYesThe release ID to patch against.
patchfileYesThe binary diff file.
messagestringNoDescription of the patch.
rollout_percentageintegerNoPercentage 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

StatusCodeDescription
400INVALID_REQUESTMissing required fields.
404RELEASE_NOT_FOUNDThe specified release does not exist.
413PATCH_TOO_LARGEPatch exceeds the maximum allowed size (10 MB).
429RATE_LIMITEDToo many patches in a short period. Wait and retry.

POST /api/v1/patches/rollback

Authenticated

Rollback 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

StatusCodeDescription
404PATCH_NOT_FOUNDThe specified patch does not exist.
409ALREADY_ROLLED_BACKThis 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

ParameterRequiredDescription
app_idYesThe application ID.
release_versionYesThe release version currently installed on the device.
patch_numberNoThe current patch number installed (if any).
platformYesDevice platform (android or ios).
archYesDevice 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

StatusCodeDescription
400INVALID_REQUESTMissing required query parameters.
404APP_NOT_FOUNDNo app matches the given ID.

Billing Endpoints

Endpoints for managing billing, viewing usage, and checking pricing tiers.

GET /api/v1/billing/pricing

Public

Retrieve 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

Authenticated

Get 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

Authenticated

Start 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

StatusCodeDescription
400INVALID_PLANThe specified plan does not exist.
409ALREADY_SUBSCRIBEDYou 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

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: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>
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 patches Dart-compiled AOT code and does not download or execute arbitrary native code.

Testing Locally

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

  1. Create a debug release:
    fcp codepush release --app-id app_7x8y9z --version 0.0.1-dev
  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:
    fcp codepush patch --app-id app_7x8y9z --release 0.0.1-dev \
        --message "Test patch"
  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.

Troubleshooting

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

Engine Artifacts Not Found

Error: 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

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> --release <version>
  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 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.