flitz publish
Build a bundle from your project against the pinned SDK and publish it; flags, apps and tracks, publish history, output streams, --json, and the on-demand SDK download.
publish is the build-and-ship command. With a pinned SDK, the whole happy
path is:
cd /path/to/your/flutter/app
flitz publish --name "Onboarding redesign"✓ Compiling 12.3s
✓ Packaging 4.1s
✓ Uploading 4.3s
Published acme_app on track feature/login in 20.9s
Landing page https://…
Deeplink flitz://download?url=…
Bundle https://….flitz
Track https://…/acme_app/feature-loginfollowed by a QR code that encodes the deeplink. --name is optional; it
labels the publish in your publish history. Track is
the track's link: it always opens the newest complete build on that track, so
you can share it once — see Links.
Open the deeplink on a device that has your host installed and the bundle downloads and runs. The easiest way to get it onto a teammate's device is the landing-page link: it opens in any browser and shows the app, this build's title, track, versions, and comment, a button and a QR code that open the deeplink in your host, and a second button that opens the newest build on the track. If the app is set to Members only, the page says the build opens only in your organization's own app, for a signed-in member.
A bundle is a complete build of your app's Dart code — never a patch — and it runs only inside a host built against the same SDK tag. See How it works for the host/bundle split.
What happens
publish:
- Reads the pin from
flitz.yamlin the current directory and uses the cached SDK for that tag, fetching it first if you allow (below). - Reads the app's
nameandversionfrompubspec.yamlin the project folder, and resolves the app key, the track, and the scheme (below). - Registers the publish with the Flitz service. This is the licensed step: an
unauthenticated or unlicensed
publishfails here with exit 3, before anything is compiled. When the SDK had to be downloaded first, a credential or license problem already stops the download, with the same exit code. - Compiling — compiles your app to a bundle with the pinned SDK. A bundle
does not depend on the target platform: the same
.flitzruns on Android and iOS hosts, and building it needs no Android SDK, NDK, or Xcode. - Packaging and Uploading — packs the bundle into a temporary file and uploads it in one request to your organization's bundle storage — Flitz's own, or a bucket you own — then deletes the file, whether or not the upload succeeded.
- Marks the publish complete, which makes its links work. The landing page needs no upload: it is built from the publish record when someone opens it.
Share within your team
Unless an Owner or Admin sets the app to
Members only (Enterprise),
anyone who holds a link can download the bundle, and a bundle runs with the full privileges of
the host. Links do not expire: they keep working after your organization's
license lapses, after the person who published leaves the organization, and
after the API key rotates. To take a build down, delete the publish —
flitz publishes delete or the portal;
deleting the app takes down all of its links, and can be undone for 30 days.
Flitz is for your own team's work-in-progress builds, reviewed by
collaborators during development; it is not a way to get finished apps to
people outside your team.
App, track, and scheme
Every publish belongs to an app of your organization, named by its key, and
to a track of that app — usually your Git branch. See
Apps and tracks for what they are and what each
link opens. publish takes each value from the first place that sets it:
| Value | Taken from, in order |
|---|---|
| App key | --app, then app: in flitz.yaml, then the name: in pubspec.yaml |
| Track | --track, then track: in flitz.yaml, then the project's current Git branch |
| Scheme | --schema, then schema: in flitz.yaml, then flitz |
Commit the values every checkout should share next to the SDK pin — see
Configuration → flitz.yaml. flitz.yaml is
read from the directory you run flitz in, also with --dir.
- New apps. The first publish under the pubspec name creates its app
without asking. A key you name yourself — with
--apporapp:— must already exist; otherwisepublishasksApp <key> does not exist in your organization. Create it? [y/N]. Declining publishes nothing. In CI, pass--yesto create it. - Tracks. A track is created by its first publish. Its key is the branch
name with every character other than letters, digits,
_, and-replaced by-, sofeature/loginpublishes to the trackfeature-login. With no branch — a detached checkout, as in most CI jobs, or a folder outside Git — the publish lands on the app'sUnassignedtrack; pass--trackinstead. - Archived and deleted apps refuse new publishes (
app <key> is archived,app <key> is deleted). Seeflitz apps.
Flags
publish accepts the global options — --json,
--yes, -v, --no-color — plus these:
| Flag | Default | Purpose |
|---|---|---|
--dir <path> | . | The Flutter project folder; must contain pubspec.yaml. flitz.yaml — the pin and the app:, track:, and schema: keys — is still read from the current directory. |
--app <key> | flitz.yaml app:, then the pubspec name: | The app this publish belongs to: 1–100 letters, digits, _, or -. A key your organization does not have yet is created only after you confirm. |
--track <name> | flitz.yaml track:, then the current Git branch | The track this publish lands on, at most 255 characters. |
--target <file> | lib/main.dart | Dart entry point, relative to --dir. It must live under lib/; any other path publishes a bundle that fails when it loads. |
--dart-define <KEY=VALUE>, -D | — | A compile-time constant, readable via String.fromEnvironment and friends. Repeatable; commas inside a value are not split. When a build fails, the build command the CLI echoes keeps each key and replaces its value with <redacted>, because defines routinely carry API keys. |
--dart-define-from-file <path> | — | A .json or .env file of compile-time constants. Repeatable. Resolved relative to the directory you ran flitz from. |
--name <text> | — | A display title for the publish, shown in the publish history and as the landing page's title. At most 120 characters. |
--version-name <text> | — | A version label shown next to the pubspec.yaml version in the history and on the landing page. At most 64 characters. |
--comment <text> | — | Free text stored with the publish, shown on the landing page, in the portal, and in flitz publishes --json. Anyone who opens the link can read it. At most 1000 characters. |
--schema <scheme> | flitz.yaml schema:, then flitz | The URL scheme of the deeplink, the QR codes, and the landing-page buttons. Lowercase, starts with a letter, then letters, digits, +, -, or . — at most 32 characters. Must match the scheme your host registers; see URL scheme. Affects only the links, never the bundle. |
--qr-output <path> | — | Also write the deeplink QR as a standalone PNG. The parent directory must exist; an existing file is overwritten. |
--no-qr | — | Suppress the inline terminal QR. The result block is still printed. |
There is no --output flag: the .flitz exists only as a temporary file
while publish uploads it.
An empty --name, --version-name, or --comment is the same as leaving it
out. A value over its limit is refused before anything is built, naming the
field — for example body/name must NOT have more than 120 characters
(exit 1); shorten it and re-run.
--dart-define values become constants in the compiled bytecode; they do not
change the deeplink or the landing page. Reserved framework-managed defines
cannot be set this way.
Write -D with a space
The CLI looks for -v in every short option, including an attached
-DKEY=VALUE. A value with a lowercase v in it — -DENV=dev, say — turns
on stack traces and retry messages, and two vs (-DAPI=https://dev.vendor.io) fail
with There is one verbosity level; pass -v (or --verbose) at most once.
Write -D KEY=VALUE with a space, or --dart-define=KEY=VALUE, and the
value is read as a value.
With a custom scheme, the deeplink, both QR codes, and the landing-page buttons
use it. A track's page uses the scheme of the track's newest publish. A host
that registers its own scheme should set schema: in the committed
flitz.yaml, so every publish uses it.
Publish history
Every publish is recorded in your organization's history: its app and track,
the app name and version from pubspec.yaml, the optional --name, --version-name, and
--comment, who published it (a person, or the organization API key), and a
state:
| State | Meaning |
|---|---|
complete | The bundle finished uploading, and publish marked the publish complete. Its links work. |
pending | Not marked complete yet: the publish is in flight, or it failed or was interrupted before publish marked it complete. |
abandoned | Still pending an hour after it started. The bundle never landed. |
A publish stays in the history until someone deletes it, or deletes its app.
List, filter, and delete them with flitz publishes, or on
the Publishes page of the portal.
Output and --json
Progress and result use different streams. The timed phases and the
Published … in … line go to stderr; the labelled result block and the QR
go to stdout. So
flitz publish > urls.txtcaptures the result block and nothing else, while the terminal still shows the
animated progress. In plain output (CI, --no-color, NO_COLOR) the phases
appear as timestamped lines and there is no QR.
The inline QR appears only when both stdin and stdout are terminals and output
is rich. Use --qr-output <file.png> to get one in every case.
The labelled block carries no line-format promise. Scripts read the result
with --json, which replaces the block with one document on stdout and
suppresses the inline QR:
flitz publish --json > result.json{ "id": "…", "page_url": "https://…", "deeplink": "flitz://download?url=…", "bundle_url": "https://…", "app": { "key": "acme_app", "name": "acme_app" }, "track": { "key": "feature-login", "name": "feature/login" }, "track_url": "https://…/acme_app/feature-login" }id identifies the publish in flitz publishes --json.
page=$(jq -r .page_url result.json)
deeplink=$(jq -r .deeplink result.json)
bundle=$(jq -r .bundle_url result.json)
track=$(jq -r .track_url result.json)On failure publish --json emits no JSON, with one exception: the
document is written before --qr-output runs, so a PNG that cannot be written
exits 2 with the complete document already on stdout. Check the exit status
first — see Exit codes.
On-demand SDK download and --yes
publish works on a fresh checkout that contains only flitz.yaml. When the
pinned SDK is not in the cache, it offers to fetch it:
The pinned SDK <tag> is not installed. Download it now (about <size>)? [Y/n]Accepting runs the download in the same invocation — without re-pinning — and
then continues the build. Declining aborts with
the pinned SDK <tag> is not installed and the flitz sdk use <tag> hint
(exit 1).
Non-interactively — a CI job, a redirected terminal — publish does not
start a large unattended download. Unless you pass --yes, it fails
immediately (exit 1) with
The pinned SDK <tag> is not installed. Download it now (about <size>)? — and there is no terminal to ask onand the hints flitz sdk use <tag> or --yes:
flitz publish --yes # fresh checkout, no prompt, one command--yes answers every prompt affirmatively — in publish that includes
creating the app when --app or app: names a key your organization does not
have yet; assume_yes: true in
config.yaml does the same permanently.
This offer exists only in publish. No other command downloads an SDK
implicitly.
A one-command CI job, on a checkout that carries only flitz.yaml:
export FLITZ_APIKEY="$FLITZ_APIKEY_SECRET" # from your CI secret store
flitz publish --yes --json --track "$BRANCH_NAME" # your CI's branch variableCI checks out a detached commit, which has no branch, so pass the branch with
--track; without it the publish lands on the app's Unassigned track.
See flitz apikey.
When it fails
Most failures follow the message convention: one sentence, then the next command. The exit code says which class of thing went wrong.
| You see | Code | Meaning |
|---|---|---|
no SDK is pinned for this project: … | 1 | No flitz.yaml with an sdk: key in the current directory. Run flitz sdk use latest here — the pin is not read from --dir. |
The pinned SDK <tag> is not installed. Download it now …? — and there is no terminal to ask on | 1 | Non-interactive without --yes. Run flitz sdk use <tag> first, or pass --yes. |
the pinned SDK <tag> is not installed | 1 | You declined the download. |
the pinned SDK <tag> could not be installed | 1 | The download started but failed; its own error is printed above. Run flitz sdk use <tag>. |
no pubspec.yaml at … | 1 | The current directory, or --dir, is not a Flutter project. |
<path>/pubspec.yaml has no `name:` (or an unusable name:) | 1 | Give the app a name: in pubspec.yaml. |
<dir> is an app directory, not an app key | 1 | --app takes a key. Pass the folder as --dir <dir>. |
the app key '<key>' from <source> must be 1 to 100 letters, digits, '_', or '-' | 1 | Fix --app, app: in flitz.yaml, or the pubspec name:. |
the track '<track>' from <source> is longer than 255 characters | 1 | Shorten --track or track:. |
the scheme '<scheme>' from <source> is not a lowercase URL scheme … | 1 | A scheme is lowercase, starts with a letter, holds only letters, digits, +, -, and ., and is at most 32 characters. |
<path>/flitz.yaml is not valid YAML (or is not a mapping, or a key is not a string) | 1 | Fix flitz.yaml, or delete it and run flitz sdk use <tag> again. |
App <key> does not exist in your organization. Create it? — and there is no terminal to ask on | 1 | A key you named is new and nobody can confirm it. Check it for typos, or pass --yes to create the app. |
the app <key> was not created | 1 | You declined the prompt. Nothing was published. |
app <key> is archived | 1 | The app refuses new publishes. Run flitz apps unarchive <key>. |
app <key> is deleted | 1 | Run flitz apps restore <key> within 30 days of the delete; after that the key is free again. |
FlutterRootVerificationException: FLUTTER_ROOT (<path>) is not a valid Flitz SDK. | 1 | The cached SDK is damaged. Run flitz sdk install --force <tag>. |
you are not logged in to Flitz / your Flitz session has ended / your organization's Flitz license is not active | 3 | Credential or license. Nothing was compiled. See Troubleshooting. |
kernel compilation failed (exit N) | 5 | The app does not compile; the compiler's diagnostics are printed above. See Troubleshooting → Build failures. |
bytecode compilation failed (exit N) | 5 | The app uses a platform API that bundles cannot use. See Troubleshooting → Build failures. |
body/name must NOT have more than 120 characters (or versionName, comment) | 1 | A --name, --version-name, or --comment value is over its limit. |
the bundle upload for <stem> failed: … | 4 | The upload did not complete. A message ending in no response within 30s means it ran out of time (below). Re-run flitz publish. |
the bundle destination for <stem> is already occupied | 4 | This publish's upload destination already holds an object. Re-run; every publish gets a new destination. |
could not write the QR PNG to <path>: … | 2 | Everything was published and printed; only the --qr-output file failed. |
The upload has 30 seconds
The bundle upload must finish within 30 seconds, and no option extends
that. On a slow connection a large bundle fails with
the bundle upload for <stem> failed: … ending in no response within 30s
(exit 4). Re-run from a faster network, or trim large assets from the app.
A publish that fails after step 3 stays in the history as pending, then
abandoned, and never changes what its track link opens. Ctrl-C during a publish stops the build tool it started, deletes
its scratch directory, and exits 2. Re-running publish always starts a fresh
publish.
Re-run with -v to watch the compilers live instead of only seeing their
output quoted on failure.