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 receive new builds of itself over the air: you run flitz publish, a
teammate scans a QR code, and the new code runs. Nothing is reinstalled. Your
normal builds carry none of the loader.
Experimental, and for your own team
Flitz is built on Dart's upstream-experimental Dynamic Modules. It 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 macOS arm64. Building the iOS host additionally needs Xcode.
- A physical device: Android arm64 (API 29 or newer) or iOS 16 or newer. The Flitz engine ships no simulator slice, so iOS work happens on a real device.
- Disk space and a few minutes: the Flitz SDK is a complete Flutter distribution, several hundred megabytes to download. The CLI tells you the size before it starts.
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 lists every readiness
check and names the command that fixes each failing one.
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.
Everything past this point needs that account: the CLI itself is free to install, but downloading an SDK, fetching the loader plugins, and publishing are gated by your organization's license.
Install the CLI
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 no Dart or Flutter SDK. Put
the install directory on your PATH and confirm:
export PATH="$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 loginThis is a device flow: the CLI prints a short code and a URL, you approve the
sign-in in a browser on any device, and the CLI stores a refresh token in
~/.config/flitz/credentials.json (mode 0600, never printed, never
committed). You do this once per machine. login also confirms that your
organization's license is active before it finishes.
Then check where you stand:
flitz statusflitz status reports every readiness check as a marked row — ✓ fine, ⚠
worth knowing, ✗ blocking, ? could not be checked — and every non-✓ row
carries a → hint naming the command that resolves it. It always exits 0;
read the markers, not the exit code. Right now it will tell you that no SDK is
pinned, which the next step fixes. See flitz status for
what each check means.
Pin an SDK
The Flitz SDK is a Flutter distribution with a DDM-enabled engine and the
bytecode compiler. Each release is tagged <flutter-version>-flz.<N>: the
Flutter version your app will build against, and a Flitz iteration counter. List
what is published and pick the release whose Flutter version matches your app:
flitz sdk releasesThen, from your app directory, pin it:
flitz sdk use latest # or an explicit tag from the list abovesdk use downloads the archive into ~/.cache/flitz/sdk/<tag>/ (after asking,
with the approximate size), verifies it, and writes the pin at your project
root:
sdk: <flutter-version>-flz.<N>Commit flitz.yaml. It is the one version your project writes down for
Flitz. 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
once the SDK is cached, re-running sdk use makes no network call at all.
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 private package repositories. Store the repository token so Gradle, CocoaPods, and SwiftPM 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 what it would store
and writes nothing.
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 CLI never
edits your project; instead it prints the exact changes as a prompt for a coding
agent, with your SDK tag, the compatible plugin version, and the repository
URLs already substituted:
flitz plugins skillHand the output to your coding agent — for example, with Claude Code from the app directory:
claude "$(flitz plugins skill)"Review the diff
This is the least-exercised step in the whole flow. The agent is editing
Gradle files, a Podfile, and your app delegate against a project layout it
has never seen. Known failure modes: inventing flavors your project already
has, overwriting AppDelegate.swift instead of merging into it, and adding
the Maven repository to one of the three places it belongs instead of all of
them. Read every change before building, and expect a round or two of
correction.
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 = [...] } in
android/app/build.gradle.kts. Prefer a flavor your project already has —
a staging one, say. Flavors not listed are byte-for-byte loader-free: no
AAR, no camera permission, no extra activities. An empty list activates the
loader nowhere, and a name that matches no flavor fails the build.
Your pubspec.yaml must declare every native plugin your bundles call. The list
of available plugins is derived from your build automatically; you do not
maintain one.
The full integration, including the manifest, scene delegate, and Info.plist
details the prompt covers, 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 — so it is safe in a shell profile. Your IDE wants the
same path, as a literal string:
In .vscode/settings.json, set dart.flutterSdkPath to the output of
flitz sdk path:
{
"dart.flutterSdkPath": "/home/<you>/.cache/flitz/sdk/<tag>"
}Build and install the host
Build the loader-bearing variant with the Flitz toolchain from the previous step and install it on a device. You do this once; from here on, new code arrives as bundles.
flutter build apk --flavor <your-loader-flavor> --release --target-platform=android-arm64
adb install build/app/outputs/flutter-apk/app-<your-loader-flavor>-release.apkadb logcat -s FlitzLoader:* flutter:* shows the loader's log once a bundle
arrives.
Publish a bundle
From the app directory:
flitz publishThe CLI compiles your app to bytecode, streams the .flitz archive to hosting
without ever writing it to disk, and prints three links plus a scannable QR
code:
Landing page https://d.flitz.dev/…
Deeplink flitz://download?url=…
Bundle https://d.flitz.dev/….flitzNow smoke-test the host: open the landing page on the device, or scan the QR
code with it, and tap the button. The page hands your app a flitz://download
link; the loader downloads the bundle and runs it. Your app on screen, arriving
from the link rather than from a build, means the embed works. From here on,
that is the whole loop — a teammate scans the QR, the loader runs the bundle.
Links are durable: they resolve for as long as the bundle exists.
Useful flags: --target <file> for a non-default entry point, -D KEY=VALUE
for compile-time constants, --qr-output <png> to save the QR code as an image,
--no-qr to skip the inline one, and --json for scripting. The full list is
on flitz publish; the link format is on
URL scheme.
Same SDK on both sides
The host and every bundle it runs must be built against the same SDK tag. The loader checks this at load time and rejects a mismatch. When you move the pin, rebuild the host once.
Publish from CI
A pipeline never signs in. Mint your organization's API key once, from an interactive session:
flitz apikey createThe plaintext prints exactly once; store it as a FLITZ_APIKEY secret in your
CI system. When that variable 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:
export FLITZ_APIKEY="${{ secrets.FLITZ_APIKEY }}"
# 1. Install the CLI
curl -fsSL https://download.flitz.dev/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# 2. Install the pinned SDK — no argument, reads flitz.yaml
flitz sdk use
# 3. Publish
flitz publish --json > urls.jsonpublish --json writes one JSON object to stdout — page_url, deeplink,
bundle_url — and nothing else; progress goes to stderr. Read it by key:
page=$(jq -r .page_url urls.json)On failure no JSON is written, so check the exit status first — the exit codes are fixed and small enough to branch on.
Cache ~/.cache/flitz/sdk between runs, keyed on the contents of flitz.yaml;
on a cache hit flitz sdk use does no network I/O. Steps 2 and 3 also collapse
into one: flitz publish --yes fetches the pinned SDK itself when it is absent.
Without --yes a non-interactive publish refuses to start a large unattended
download and fails with a hint naming flitz sdk use — keep the steps separate
if you want the download to fail as its own step.
There is one key per organization, shared by every pipeline, so
flitz apikey rotate and flitz apikey revoke cut off all of them at once —
update every pipeline after a rotation. flitz apikey status shows the key
obfuscated. Key management requires an interactive login; a pipeline
authenticated only by FLITZ_APIKEY cannot mint, rotate, or revoke. See
flitz apikey.
Next steps
flitz status
Every readiness check, what each verdict means, and the command that fixes it.
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
Why the host and the bundle must agree, and what the compatibility check guarantees.