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 publishneeds 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:
- Sign in to the Flitz portal at app.flitz.dev with the same account.
- 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 | shThe 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 --versionPrefer 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 loginThe 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 statusIt 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 releasesThen, 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 releaseOnly 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:
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 --writeThis 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 appReview 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> suffixflitz 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:
{
"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.apkFlutter 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 publishThe 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 createThe 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:
# 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.jsonSet 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 trackOn 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
flitz status
Every readiness check, what each verdict means, and the command that fixes it.
Portal
Publishes by app and track, who can download them, members, the API key, and billing.
Loader plugins
The full Android and iOS integration behind the prompt you applied.
CLI troubleshooting
Build failures, license errors, and what to do about each.
How it works
Where a bundle runs, what must match between host and bundle, and the limits.