New in v2.0.0

Runtime Bundles

Push config overrides, feature-flag overrides, directives, and assets to your Flutter app instantly — no App Store or Play Store release required.

Runtime Bundles are one of the two Code Push modes. They update configuration and assets, not code. To fix your compiled Dart logic over the air, see VM Patches.

What Code Push does

Koolbase Code Push lets you deploy Runtime Bundles — versioned, signed packages containing config overrides, feature flag overrides, directives, and assets. On cold launch, the SDK checks for an available bundle, downloads it in the background, verifies its integrity, and activates it on the next launch.

Once a bundle is active, all existing SDK accessors — Koolbase.configInt(), Koolbase.isEnabled(), etc. — transparently return bundle values. No code changes required in your app.

What can be updated via a Runtime Bundle

  • Config values (timeouts, limits, URLs, thresholds)
  • Feature flag overrides (enable/disable features per bundle)
  • Directives (fire-once commands like force_logout_all)
  • Assets (images, JSON files, fonts)
  • Compiled Dart code (use VM Patches instead — Runtime Bundles don't touch code)
  • Native plugins or Android/iOS permissions (require a store release)

App Store compliance

Runtime Bundles only update config, flags, and asset files — never native binaries or compiled code — so they sit comfortably within Apple App Store and Google Play Store rules. For the full picture across both Code Push modes, see Store Compliance.

Shipping raw files

Code Push applies config, flag, and directive values for you. If you instead need to ship raw files — content packs, localized JSON, images — and read them yourself, use Storage.

How it works

  1. You deploy a bundle via the Koolbase dashboard or CLI
  2. On cold launch, the SDK calls GET /v1/code-push/check
  3. If a newer bundle exists for this app version, platform, and channel — the SDK downloads it
  4. The SDK verifies the SHA-256 checksum before caching
  5. On the next cold launch, the bundle is promoted to active and its values override Remote Config and Feature Flags

Merge precedence

Bundle values win over all other sources. The full precedence chain is:

App defaults → Remote Config → Runtime Bundle (wins)

Only keys present in the bundle are overridden — the bundle does not wipe your Remote Config layer.

Setup

Add codePushChannel to your KoolbaseConfig. That's the only change required.

await Koolbase.initialize(KoolbaseConfig(
  publicKey: 'pk_live_xxxx',
  baseUrl: 'https://api.koolbase.com',
  codePushChannel: 'stable', // or 'beta'
));

// After initialize() returns, bundle overrides are already active.
// Use SDK accessors as normal — they transparently return bundle values.
final timeout = Koolbase.configInt('api_timeout_ms', fallback: 3000);
final newCheckout = Koolbase.isEnabled('new_checkout_flow');

Activation timing

Bundles activate on the next cold launch, not immediately. On the current launch, the SDK downloads the bundle in the background. On the following launch, it is promoted to active before the first frame renders.

Bundle format

A Runtime Bundle is a .zip file with a specific structure. When using the CLI, this is created automatically. When building manually, follow this layout:

bundle/
├── config.json       # Config overrides (key-value pairs)
├── flags.json        # Flag overrides (key: true/false)
├── directives.json   # Fire-once commands
└── assets/
    ├── images/
    ├── json/
    └── fonts/
// config.json
{
  "api_timeout_ms": 8000,
  "max_retry_count": 5,
  "support_url": "https://help.myapp.com"
}

// flags.json
{
  "new_checkout_flow": true,
  "legacy_auth": false,
  "dark_mode_beta": true
}

// directives.json
{
  "force_logout_all": false
}

Deploying via CLI

The koolbase bundle deploy command packages your bundle directory, creates a draft, uploads the artifact, and publishes — all in one command.

koolbase bundle deploy \
  --app <project_id> \
  --platform ios \
  --channel stable \
  --base-app-version 1.2.0 \
  --max-app-version 1.2.99 \
  --bundle-dir ./bundle \
  --rollout 10

The --rollout flag controls what percentage of eligible devices receive this bundle. Start with a low percentage and increase as confidence grows.

# List all bundles for an app
koolbase bundle list --app <project_id>

# Recall a published bundle (devices revert on next cold launch)
koolbase bundle recall --app <project_id> --bundle <bundle_id>

# Dry run — validate and package without uploading
koolbase bundle deploy ... --dry-run

Deploying via Dashboard

  1. Go to your project → Code Push
  2. Select the channel (stable or beta) and platform
  3. Click Deploy Bundle
  4. Fill in the version range, rollout percentage, config JSON, and flags JSON
  5. Click Deploy — the bundle is live immediately

Version range

base_app_version and max_app_version define which app versions can receive this bundle. A bundle targeting 1.2.0 → 1.2.99 will never be served to a user on 1.3.0. Both fields are required — there are no open-ended bundles.

Rollout & recall

Rollout is controlled at the bundle level. A bundle with rollout_percentage: 10 is served to 10% of eligible devices — deterministically, based on device ID. The same device always falls in or out of the rollout for a given bundle.

Only one bundle can be published per app + platform + channel at a time. Publishing a new bundle automatically recalls the previous one.

To roll back, recall the current bundle from the dashboard or CLI. Devices on that bundle will revert to the previous archived version on their next cold launch.

Rollback behaviour

When a bundle is recalled, devices receive a rollback response on their next check. The SDK deletes the active bundle and restores the archived previous version. If no previous version exists, the app falls back to its built-in defaults.

Mandatory updates

Mark a bundle mandatory when every device must apply it before continuing — for example, to force everyone off a broken config. Set it with the Force update toggle in the dashboard (on the deploy form, or per-bundle in the detail panel), or via PATCH /v1/apps/{app_id}/bundles/{bundle_id}/mandatory. Requires koolbase_flutter 4.1.0+ or koolbase-react-native 2.4.0+.

A mandatory bundle still downloads in the background and activates on the next cold launch like any other. What changes is that the SDK lets you react to it, so you can prompt the user to restart now instead of waiting. There are two ways to consume it:

await Koolbase.initialize(KoolbaseConfig(
  publicKey: 'pk_live_xxxx',
  baseUrl: 'https://api.koolbase.com',
  codePushChannel: 'stable',
  // Push: fires the moment a mandatory bundle is staged for next launch
  onMandatoryUpdate: (info) {
    showRestartRequiredDialog(version: info.version);
  },
));

// Poll: read it later — e.g. when the app resumes — before letting
// the user proceed into a critical flow
if (Koolbase.codePush.hasMandatoryUpdate) {
  showRestartRequiredDialog();
}

Callback vs flag

onMandatoryUpdate fires once, the instant a mandatory bundle finishes staging — best for prompting immediately. hasMandatoryUpdate is a flag you can read at any time — best for gating before a critical screen. Use either or both.

They signal, they don't apply

Neither the callback nor the flag applies the bundle for you. The bundle activates on the next cold launch, so your handler typically prompts the user to restart the app so the required update takes effect.

Accessing bundle data directly

You can access the code push client directly if you need more control.

// Check if a bundle is active
if (Koolbase.codePush.hasActiveBundle) {
  final manifest = Koolbase.codePush.activeManifest!;
  print('Bundle v${manifest.version} active');
  print('Config: ${manifest.payload.config}');
  print('Flags: ${manifest.payload.flags}');
}

// Register a directive handler
Koolbase.codePush.onDirective('force_logout_all', (value) {
  if (value == true) {
    Koolbase.auth.signOut();
  }
});

// Access override engine directly
final override = Koolbase.codePush.override;
final timeout = override.getConfig('api_timeout_ms');
final flagEnabled = override.getFlag('new_checkout_flow');

Channels

Channels let you control which bundles different users receive. The most common setup:

ChannelUse case
stableProduction users. Deploy here when confident.
betaInternal testers or early adopters. Deploy here first.

Set the channel in KoolbaseConfig at initialization time. Bundle versions are independent per channel — stable v3 and beta v3 are separate sequences.

API reference

Method / PropertyDescription
Koolbase.codePushAccess the code push client.
Koolbase.codePush.hasActiveBundleTrue if a bundle is currently active.
Koolbase.codePush.activeManifestThe active BundleManifest, or null.
Koolbase.codePush.hasMandatoryUpdateTrue if a mandatory bundle is staged and awaiting the next cold launch.
Koolbase.codePush.overrideThe RuntimeOverrideEngine instance.
Koolbase.codePush.onDirective(key, handler)Register a handler for a directive key.
KoolbaseConfig.onMandatoryUpdateCallback fired the moment a mandatory bundle is staged.
Koolbase.configInt(key)Returns bundle value if present, else Remote Config, else fallback.
Koolbase.configString(key)Same precedence chain as configInt.
Koolbase.configBool(key)Same precedence chain as configInt.
Koolbase.isEnabled(flagKey)Returns bundle flag if present, else Feature Flag evaluation.