Skip to content

Scripting the Shorebird CLI

The Shorebird CLI can run without a person at the keyboard, in a CI job, a script, or a coding agent. This page covers how it behaves there, how to get machine-readable output, and how to run destructive commands safely.

Shorebird stops asking questions when any of these is true:

  • The global --json flag is passed.
  • stdout or stdin isn’t a terminal, for example when output is piped to a file.
  • A CI environment variable is set, such as CI (with any value), BOT=true, or the variables set by GitHub Actions, Azure Pipelines, Travis, AppVeyor, Cirrus CI, AWS CodeBuild, or Jenkins.

In non-interactive mode, nothing waits for input:

  • Some prompts use a default. shorebird init uses your pubspec name as the app’s display name. shorebird patch without --release-version builds the patch with the latest Flutter version to work out which release it belongs to, which can pick the wrong release or Flutter version.
  • Other prompts fail. When there’s no safe default, the command exits with an error and a hint, usually naming the flag that provides the answer. For example, shorebird patch fails on native code changes unless you pass --allow-native-diffs, and shorebird init fails if you belong to several organizations and don’t pass --org-id.
  • Progress spinners become plain lines when stdout isn’t a terminal or --json is passed. Each step prints Starting ... and then Done ... or Failed ... to stderr.

To keep a job from failing or guessing, pass every input as a flag: the app with --app-id or --flavor, and the release with --release-version. For shorebird patch, --release-version=latest targets the most recently updated release. Authenticate with an API key in the SHOREBIRD_TOKEN environment variable, since shorebird login needs a browser. In GitHub Actions, a job can sign in with OIDC instead, with Shorebird 1.6.129 or newer.

Pass the global --json flag to get one JSON object on stdout instead of human-readable text. --json also turns on non-interactive mode and removes color codes.

These commands return structured JSON:

GroupCommands
shorebird accountwhoami, update, notifications, orgs
shorebird authstatus, token
shorebird appslist, rename, delete, transfer
shorebird invitationslist, accept, decline
shorebird channelslist, create, delete
shorebird patcheslist, info, set-track, promote, rollback, rollforward
shorebird releaseslist, info, delete
shorebird metricsactive-hours, adoption, downloads, heatmap, installs, new-devices, users, versions
Othershorebird release, shorebird patch, shorebird doctor, shorebird flutter versions list, shorebird --version

Every other command, such as shorebird init or shorebird cache clean, also accepts --json and runs non-interactively. Its logs go to stderr, and when it succeeds it prints a success object whose data is empty ({}), so stdout always holds exactly one JSON object. --help with --json works the same way: the usage text goes to stderr. This needs Shorebird 1.6.129 or newer; earlier releases leave stdout empty when these commands succeed.

shorebird api is the exception: it prints the API response body as-is and rejects --json. See Calling the API from the command line.

A successful command prints an object with status set to success. The command’s result is in data, and meta says which CLI version and command produced it:

shorebird --version --json
{
  "status": "success",
  "data": {
    "shorebird_version": "1.6.123",
    "flutter_version": "3.47.5",
    "flutter_revision": "ff900e7fbab20fdeb40905ee631baa8d49bd0fa1",
    "engine_revision": "c663a2682e6adfc60a1aaff99ff62baa891dcf87"
  },
  "meta": { "version": "1.6.123", "command": "version" }
}

The fields inside data depend on the command.

A failed command prints an object with status set to error and exits with a non-zero code:

{
  "status": "error",
  "error": {
    "code": "fetch_failed",
    "message": "Failed to fetch channels for app \"<app-id>\"."
  },
  "meta": { "version": "1.6.123", "command": "channels list" }
}

error.code is one of:

CodeMeaning
usage_errorInvalid or missing arguments. Exit code 64.
interactive_prompt_requiredThe command needed an answer to a prompt. Exit code 64.
fetch_failedShorebird couldn’t retrieve something it needed, such as an app.
precondition_failedA check before the command ran failed, such as not being logged in or not finding shorebird.yaml.
software_errorThe command failed unexpectedly.
process_exitThe command stopped with a non-zero exit code.

When there’s a way to recover, error.hint says what to do, such as the flag to add.

When the Shorebird API rejected the request, error.api_code holds the server’s error code, such as shorebird_plan_insufficient_tier for a feature your plan doesn’t include, and error.details holds any extra detail the server sent. Branch on api_code rather than on the message text. These fields need Shorebird 1.6.129 or newer.

Commands that delete something ask for confirmation in a terminal. Without a terminal, in CI, or with --json, they never prompt, so confirm with a flag or the command fails with a usage error and nothing is deleted:

  • shorebird releases delete and shorebird channels delete: pass --yes.
  • shorebird apps delete: pass the app’s display name with --confirm-name. Deleting an app has no --yes.
shorebird releases delete --app-id <app-id> --release-version 1.0.0+1 --yes --json
shorebird apps delete --app-id <app-id> --confirm-name "<display name>" --json

This behavior needs Shorebird 1.6.129 or newer. Earlier releases confirmed every delete with --confirm-name.

shorebird patches rollback and shorebird patches rollforward succeed when the patch is already in the requested state, so a retried job doesn’t fail. Pass --require-change to make the command exit with a usage error when nothing changed, if your script needs to know. With --json, the data.changed field reports the same thing:

shorebird patches rollback --release-version 1.0.0+1 --patch-number 2 --json

See Roll back from the CLI for the command’s options, and What happens when a patch is rolled back? for what it does on devices.