Flitzdocs

Getting started

From an empty machine to a bundle running on a real device.

This page takes you from a fresh machine to a bundle running on a phone. When you finish, one build of your app — the host — handles flitz:// links and can load new builds of itself from a link: you run flitz publish, a teammate opens the link on their device, and the new code runs. Nothing is reinstalled. Your normal builds carry none of the loader.

For development, and for your own team

Flitz belongs in your development and test loop — your production build ships the normal way and is never touched. A bundle is a complete build of your app, never a patch, and it runs with the full privileges of the host, so publish only bundles your own team built.

Before you begin

You need:

  • A Flutter app to preview. The host is built from it, and the bundles are built from it too.
  • A supported development machine: Linux x64 or an Apple Silicon Mac. Publishing bundles works from either, and a bundle published on Linux runs on iOS hosts too. Building the iOS host needs the Mac. Windows, Intel Macs, and Linux arm64 are not yet supported.
  • The usual Flutter toolchain, for building the host: the Android SDK and a JDK for Android; Xcode, CocoaPods, and a signing identity for your device for iOS. flitz publish needs none of it, so a machine or CI job that only publishes bundles can skip it.
  • A device to run the host on: an Android phone or tablet with a 64-bit ARM CPU on API 29 or newer, an x86_64 Android emulator, or an iPhone or iPad on iOS 16 or newer. The loader has no iOS Simulator support, so iOS work happens on a real device. 32-bit Android devices cannot run bundles.

The steps are ordered so that each one can be checked before the next. Whenever you are unsure where you stand, run flitz status — it checks that your Flitz installation is in order and names the next step for anything that is not.

Get an account

Flitz is in early access. Request access through the contact page on flitz.dev. You will receive an invitation email for your organization; accept it and set a password. Check your spam folder if it does not arrive.

The first person invited to a new organization is its Owner. Before anyone in the organization can download an SDK, fetch the loader plugins, or publish, the Owner starts the subscription:

  1. Sign in to the Flitz portal at app.flitz.dev with the same account.
  2. On Billing, choose Subscribe. Your organization's first subscription starts with a 30-day free trial; checkout asks for a card. If billing was arranged with Flitz directly, the page says so and asks for nothing.

Joining an organization that already exists? Ask its Owner or an Admin to invite you from the portal's Members page; the subscription is theirs to manage.

The CLI itself is free to install, but everything past the next step needs the account and an active license.

Install the CLI

The flitz CLI is a standalone program: it needs no Dart or Flutter SDK to run. Install it with:

curl -fsSL https://download.flitz.dev/install.sh | sh

The script detects your platform, downloads the matching binary, verifies its SHA-256, and installs flitz into $XDG_BIN_HOME if set, otherwise ~/.local/bin. It needs no elevated privileges, and it prints where it put the binary. If that directory is not on your PATH, add it in your shell profile, then confirm:

export PATH="${XDG_BIN_HOME:-$HOME/.local/bin}:$PATH"
flitz --version

Prefer to install by hand? The download page at download.flitz.dev lists each platform's binary with its checksum and a copy-pasteable verify command.

Sign in

flitz login

The CLI prints a link and a short code, and opens the link in your browser when it can. Approve the sign-in in a browser on any device, and the CLI stores your session. You do this once per machine.

Then check where you stand:

flitz status

It checks that your Flitz installation is in order. Right now it reports that no SDK is pinned and that no loader-plugin credentials are stored; the next two steps fix both. See flitz status for what each check means.

Pin an SDK

The Flitz SDK is a custom build of Flutter that Flitz needs. Each release is tagged <flutter-version>+flz.<N>: the Flutter version it is built from, then the Flitz iteration. List what is published:

flitz sdk releases

Then, from your app directory, pin the release whose Flutter version matches your app — copy the tag exactly as the list prints it:

flitz sdk use <tag>  # or `flitz sdk use latest` to pin the newest release

Only the Flutter versions flitz sdk releases lists are available. If yours is not there, pick the closest one your app's environment: flutter: constraint allows, or contact support.

sdk use writes the pin to flitz.yaml in the current directory, then offers to download the SDK:

flitz.yaml
sdk: <flutter-version>+flz.<N>

Run sdk use, and every later flitz command, from the directory that holds flitz.yaml. The same file can also name the app and a fixed track your publishes go to — see Configuration → flitz.yaml.

Commit flitz.yaml. It is the SDK version your project writes down. A teammate's fresh checkout reproduces the exact same SDK with a bare flitz sdk use, and a CI job does the same. The download is resumable, and a cached SDK is not downloaded again — sdk use still asks the Flitz service about the tag, so it needs network access and an active license. The loader plugin's version lives in your Gradle, Podfile, or Package.swift files; the next steps set it.

Pins are deliberate

flitz status reports when a newer release exists than the one you pin, but no command nags you about it. Move the pin when your app moves to a new Flutter version, not because a newer tag appeared.

See flitz sdk for the other subcommands.

Store the loader-plugin credentials

The loader plugins live in a private package repository. Store the repository token so Gradle and CocoaPods authenticate on their own:

flitz plugins credentials --write

This writes the token to ~/.gradle/gradle.properties (as flitzMavenToken) and a machine entry to ~/.netrc, atomically and at mode 0600, preserving everything else in those files. Both are machine-local; never copy the token into a committed file.

Without --write the command prints its plan — including the token itself — and writes nothing, so do not run it where output is logged or shared.

If you add the loader on iOS through Swift Package Manager, also register the Flitz package registry and sign in to it:

swift package-registry set --global --scope leancode https://swift.cloudsmith.io/leancode/flitz/
swift package-registry login https://swift.cloudsmith.io/leancode/flitz/ \
  --username token --password "$(flitz plugins credentials --print-token)"

Embed the loader

The loader is a native plugin — a Gradle plugin plus AAR on Android, an xcframework on iOS — that you add to one build of your app. The quickest way to wire it in is to hand the job to your favorite coding agent. From the app directory, give it a prompt like:

Read https://docs.flitz.dev and embed the loaders into my app

Review the diff

The agent is editing Gradle files, a Podfile, and your app delegate against a project layout it has never seen. Read every change before building, and expect a round or two of correction.

The loader needs Android minSdk 29 and an iOS deployment target of 16.0, which are higher than a new Flutter project's defaults. Set minSdk = 29 in defaultConfig or in your loader flavor in android/app/build.gradle.kts, and on iOS set platform :ios, '16.0' in the Podfile and the Runner target's minimum deployment to 16.0.

Two decisions the agent cannot make for you:

The Gradle plugin is always applied, but the loader is active only in the product flavors you list in flitz { enabledFlavors = listOf("staging") } in android/app/build.gradle.kts. Prefer a flavor your project already has — a staging one, say. Flavors you do not list contain nothing from Flitz. Every Gradle build of the app, unlisted flavors and CI included, still needs the repository token, because the plugin itself comes from the private repository.

One more decision is optional, on Flitz Enterprise: sign-in. A host with it signs each teammate in with their Flitz account before every load, and only such a host opens builds of an app set to Members only in the portal. It needs a client id, which Flitz issues for your app on request — see Sign-in and the panel. You can add it later.

The full integration, including the manifest, scene delegate, and Info.plist details, is in the loader plugins section: Android and iOS.

Point your toolchain at the SDK

flitz publish finds the pinned SDK on its own. Building the host does not — the flutter and dart you build with must be the Flitz ones, because stock Flutter does not ship the engine the loader needs.

export PATH="$(flitz sdk path)/bin:$PATH"
flutter --version        # the framework version carries the +flz.<N> suffix

flitz sdk path prints the pinned SDK's root. It reads flitz.yaml from the current directory, makes no network call, and answers whether or not the SDK has been downloaded yet. Run the export from the project directory, or put it in a per-project environment file such as direnv's .envrc; in a global shell profile it works only when the shell starts in the project. Your IDE wants the same path, as a literal string:

In your VS Code settings, set dart.flutterSdkPath to the output of flitz sdk path — on Linux /home/<you>/.cache/flitz/sdk/<tag>, on macOS /Users/<you>/.cache/flitz/sdk/<tag>. The path differs per machine, so prefer your user settings over a committed workspace file:

settings.json
{
  "dart.flutterSdkPath": "<output of flitz sdk path>"
}

Build and install the host

Build the variant of your app that includes the Flitz loader, using the toolchain from the previous step, and install it on a device. You do this once, and again whenever the host's native side changes; day-to-day changes arrive as bundles.

Release builds only

Build the host in release mode. A debug build — including a plain flutter run — cannot run bundles: on Android the bundle opens to a blank screen, and on iOS the loader reports "Bundle engine did not start". To iterate on the host itself, use flutter run --release.

flutter build apk --flavor <your-loader-flavor> --release
adb install build/app/outputs/flutter-apk/app-<your-loader-flavor>-release.apk

Flutter lowercases the flavor in the file name — --flavor withLoader writes app-withloader-release.apk — and flutter build prints the exact path. adb logcat -s FlitzLoader:* flutter:* shows the loader's log once a bundle arrives.

Publish a bundle

From the app directory:

flitz publish

The CLI builds a bundle of your app, uploads it, and prints four links, plus a QR code when your terminal can show one:

Published <app-key> on track <branch> in 17.2s

  Landing page  https://d.flitz.dev/…
  Deeplink      flitz://download?url=…
  Bundle        https://d.flitz.dev/….flitz
  Track         https://d.flitz.dev/…

Every publish belongs to an app and a track. Your first publish creates the app in your organization, keyed by the name: in your pubspec.yaml, and files the publish on a track named after your current Git branch. The first three links open this one publish. The Track link opens the track's page, whose button always opens the newest build on that branch, so you can share it once. See Apps and tracks.

On the device with your host installed, open the landing page link and tap the open button on its This build card. The host downloads the bundle, checks it, and runs it — your app on screen, arriving from the link, means the embed works. On iOS the bundle takes over the app; relaunch it to get back to your own UI. If nothing opens, no app on the device handles flitz:// links: install your host first.

From here on, that is the whole loop: you publish, a teammate opens the link, and the host runs the bundle. Each teammate needs your host on their device; get it to them through TestFlight, a Google Play internal testing track, or Firebase App Distribution — see Get the host to your team. With the defaults they need no Flitz account; they need one if your host has sign-in, or if the app is set to Members only in the portal.

Links have no expiry date: they work as long as the publish and its app exist, and deleting either one stops them. By default anyone who holds a link can download the build, so share links only within your team, or have the Owner or an Admin set the app to Members only, on Flitz Enterprise — see Download access. Every publish is also listed on the portal's Publishes page and under its app on Apps. See flitz publish for more.

Rebuild the host when its native side changes

The loader refuses a bundle built with a different SDK pin, or one that needs a package with native code the host lacks. Rebuild and reinstall the host whenever you move the pin or add or upgrade such a package.

Publish from CI

CI jobs don't run flitz login; they authenticate with your organization's API key instead. Create the key once, on your own machine where you are signed in:

flitz apikey create

The Owner or an Admin can also create it from Settings in the portal. The plaintext shows exactly once; store it as a FLITZ_APIKEY secret in your CI system. When FLITZ_APIKEY is set, the whole CLI authenticates with it and flitz login is skipped. A job checks out the repository (which carries flitz.yaml), installs the CLI, installs the pinned SDK, and publishes. It needs no Android SDK, JDK, or Xcode, so it can run on a Linux x64 runner:

ci.sh
# FLITZ_APIKEY comes from your CI system's secret store.
: "${FLITZ_APIKEY:?set FLITZ_APIKEY from your CI secret store}"

# 1. Install the CLI
curl -fsSL https://download.flitz.dev/install.sh | sh
export PATH="${XDG_BIN_HOME:-$HOME/.local/bin}:$PATH"

# 2. Install the pinned SDK — no argument, reads flitz.yaml
flitz sdk use

# 3. Publish. Name the track: a CI checkout is often a detached commit with no branch.
flitz publish --json --track "$BRANCH_NAME" > urls.json

Set BRANCH_NAME from your CI system's branch variable; without a branch, a publish lands on the app's Unassigned track. If flitz.yaml names an app: your organization does not have yet, add --yes so the job may create it. publish --json writes one JSON object to stdout — id, page_url, deeplink, bundle_url, app, track, and track_url — and nothing else; progress goes to stderr. Read it by key:

page=$(jq -r .page_url urls.json)
track=$(jq -r .track_url urls.json)   # always opens the newest build on the track

On failure no JSON is written, so check the exit status first — the exit codes are fixed and small enough to branch on.

That's it!

You have a host on your device and a bundle running in it. From now on, flitz publish is all it takes to put your latest changes on your team's devices for review.

Next steps

On this page