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 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 | 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 no Dart or Flutter SDK. Put the install directory on your PATH and confirm:

export PATH="$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

This 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 status

flitz 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 releases

Then, from your app directory, pin it:

flitz sdk use latest        # or an explicit tag from the list above

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

flitz.yaml
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 --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 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 skill

Hand 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> 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 — 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:

.vscode/settings.json
{
  "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.apk

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 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/….flitz

Now 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 create

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

ci.sh
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.json

publish --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

On this page