---
title: "Tracks"
description: "Use tracks to control which devices receive a patch, enabling staged rollouts and internal testing workflows."
---

**Tracks** are named deployment channels that control which devices receive a patch. Every Shorebird app has a built-in `stable` track, which is the default channel that all devices subscribe to. You can create additional tracks on the fly, simply by naming them when publishing a patch. No configuration or registration is needed; a track is created the first time you publish to it. To create, list, or delete tracks yourself, see [Managing tracks from the CLI](#managing-tracks-from-the-cli).

Note

The CLI calls tracks **channels** in some places, such as the `shorebird channels` commands. Track and channel mean the same thing.

## How tracks work

When your app starts up, the Shorebird updater asks the server: *“Is there a new patch on the **stable** track for this release version?”* If you have integrated `package:shorebird_code_push`, your app can instead ask for patches on a different named track. This makes it possible to route specific devices (e.g., your QA team’s phones) to a `beta` or `staging` patch without affecting production users.

```
flowchart LR
    A["patch --track=staging"] --> B{"Validate"}
    B -- "Issues" --> A
    B -- "OK" --> C["Promote to stable"]
    C --> D["All devices update"]
```

## Built-in vs. custom tracks

| Track      | Description                                                                                                                  |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `stable`   | The track devices check by default. All devices receive patches from this track unless they explicitly subscribe to another. |
| `staging`  | Commonly used for internal / CI validation before promoting to production.                                                   |
| `beta`     | Commonly used for wider pre-production testing (e.g., QA teams, opt-in testers).                                             |
| *Any name* | Custom tracks are created on demand, or with `shorebird channels create`.                                                    |

Note

Track names are arbitrary strings. `shorebird channels create` and `shorebird patches set-track` accept names of up to 128 characters. Use whatever naming convention makes sense for your team. `stable`, `beta`, and `staging` are built-in names, so those tracks can’t be deleted.

## Publishing a patch to a track

[Watch the video: Push a patch to the staging track](/videos/staging-patch.mp4)

Pass the `--track` flag to any `shorebird patch` command. If you omit the flag in a terminal, Shorebird 1.6.135 or newer asks which track to publish to, with `staging` selected. In CI, with `--json`, or anywhere else it [can’t ask](/code-push/ci/scripting#non-interactive-mode), it publishes to `stable` and warns that a future release will require `--track`; pass `--track=stable` to keep that behavior. A `--dry-run` without `--track` publishes nothing and doesn’t ask. Older CLIs publish to `stable` without asking.

With Shorebird 1.6.130 or newer, `--channel` works as another name for `--track` here and on `shorebird preview`, `shorebird patches set-track`, and `shorebird patches promote`.

* Android

  Android

  ```
  shorebird patch android --track=staging
  ```

* iOS

  iOS

  ```
  shorebird patch ios --track=staging
  ```

* macOS

  macOS

  ```
  shorebird patch macos --track=staging
  ```

You can use any custom track name:

```
shorebird patch android --track=beta
shorebird patch android --track=qa-team
```

## Previewing a patch on a specific track

Use `shorebird preview` with the `--track` flag to run your app locally against a specific track’s patches:

```
shorebird preview \
  --app-id <your-app-id> \
  --release-version 1.0.0+1 \
  --track staging
```

This downloads the release and runs it on a connected device or emulator, applying the latest patch available on the specified track.

## Promoting a patch between tracks

Once you’ve validated a patch on a staging track, you can promote it to `stable` without rebuilding.

Promoting moves the patch: it leaves its old track, since a patch is on one track at a time. Devices get the highest-numbered patch on their track that isn’t rolled back, so promoting a patch with a lower number than the track’s current patch changes nothing for devices on that track, while its old track loses the patch. To ship that fix, publish it as a new patch, or [roll back](/code-push/rollback/) the higher-numbered patch.

### Via the Shorebird console

Navigate to the release details page, click the **⋮** context menu next to the patch, choose **Change Track**, and select `stable` in the dialog that appears. The dialog lists every track in the app, so you can move a patch to any of them the same way.

### Via the CLI

Promote a patch directly to `stable`:

```
shorebird patches promote \
  --release-version 1.0.0+1 \
  --patch-number 1
```

`promote` is `set-track` with the track defaulting to `stable`. Older CLIs run the same command but print a deprecation warning, and its `--track`, `--channel`, `--app-id`, and `--json` flags need Shorebird 1.6.130 or newer.

To move a patch to any other track, pass that track’s name to `--track`:

```
shorebird patches set-track \
  --release-version 1.0.0+1 \
  --patch-number 1 \
  --track beta
```

The `--release-version` and `--patch-number` flags of `set-track` need Shorebird 1.6.128 or newer. On an older CLI, use `--release` and `--patch` instead, or run `shorebird upgrade` first.

## Subscribing a device to a track

By default, devices always subscribe to the `stable` track. To have a device check a different track, you must:

1. Add [`package:shorebird_code_push`](https://pub.dev/packages/shorebird_code_push) to your app.
2. Set `auto_update: false` in your `shorebird.yaml` to disable the default background updater.
3. Call the updater manually with a `track` argument.

shorebird.yaml

```
app_id: your-app-id-here
auto_update: false
```

```
import 'package:shorebird_code_push/shorebird_code_push.dart';

final updater = ShorebirdUpdater();

Future<void> updateFromBeta() async {
  // Subscribe this device to the beta track
  await updater.update(track: UpdateTrack.beta);
}

Future<void> updateFromQaTeam() async {
  // Or use a custom track name
  await updater.update(track: UpdateTrack('qa-team'));
}
```

Tip

`UpdateTrack.stable`, `UpdateTrack.beta`, and `UpdateTrack.staging` are convenience constants. For any other track name, use `UpdateTrack('your-track-name')`. Custom track names need `shorebird_code_push` 2.0.4 or newer; older versions only offer `stable`, `beta`, and `staging`.

## Switching a device between tracks

The updater doesn’t remember which track a device asked for. Each `update(track: ...)` call checks the track you pass it. The automatic check at launch always uses `stable`, except in a build installed with `shorebird preview --track`, which checks the previewed track. To keep a device on a track, save the device’s track in your app, pass it on every `update` call, and set [`auto_update: false`](/code-push/shorebird-yaml/#auto_update).

In this table, a device runs patch 3 from `beta`, and `stable` has patch 2. Shorebird follows the track, not the highest patch number. A newly installed patch runs after the next restart. Where a row depends on a version, it’s the Flutter version the release was built with.

| Situation                                                 | What the device runs                                                                                                    |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Your app calls `update(track: UpdateTrack.stable)`.       | Patch 2, the latest patch on `stable`, even though it’s older.                                                          |
| Your app asks for a track with no patch for this release. | Patch 3, the patch it has. It doesn’t go back to the base release or fall back to `stable`.                             |
| After moving to patch 2, it’s offered patch 3 again.      | 3.41.9 or newer: patch 2, until its track gets a patch newer than 3. Older: patch 3.                                    |
| Your app calls `update` for two tracks before a restart.  | The patch from the last call that installed one.                                                                        |
| `auto_update` is on, and your code checks another track.  | 3.41.9 or newer: it can get stuck on one track’s patch. Older: it moves back and forth between the two tracks’ patches. |

A device is offered patch 3 again when it asks for `beta` again or you promote patch 3 to `stable`. On 3.41.9 or newer, the updater sees patch 3 as already installed and stays on patch 2.

To reset a device stuck on an older patch, clear the app’s data. On Android, open **Settings > Apps**, pick the app, and choose **Storage > Clear storage**, or run `adb shell pm clear` with the app’s package name. Uninstalling and reinstalling is not enough on Android when Auto Backup is on, because the restore can bring the installed patches back. On iOS, delete the app and install it again.

Rolling back a patch applies to that patch on every track, not to one track. See [Does rolling back a patch on one track affect other tracks?](#does-rolling-back-a-patch-on-one-track-affect-other-tracks)

## Listing patches by track

To see all patches for a release, including their track assignment, run:

```
shorebird patches list --release-version 1.0.0+1
```

Each line shows the patch’s ID, its number, and its track, or `[no track]` if it’s on none. A rolled back patch is marked `[rolled back]`:

```
42  #1  track: stable
43  #2  [no track]
44  #3  track: beta  [rolled back]
```

## Managing tracks from the CLI

As of Shorebird 1.6.121, the `shorebird channels` commands list, create, and delete an app’s tracks. Like other app commands, they read the app ID from `shorebird.yaml` in the current directory. Pass `--app-id` to run them from anywhere else, or `--flavor` to pick a flavor’s app ID. All of them support the global `--json` flag.

### List tracks

```
shorebird channels list
```

```
1  stable
2  beta
```

Each line shows the track’s ID and name.

### Create a track

```
shorebird channels create --name qa
```

You don’t need to create a track before publishing to it, since `shorebird patch --track=<name>` creates it for you. Use `channels create` when you want the track to exist before its first patch. The command fails if a track with that name already exists.

### Delete a track

Deleting a track deletes it and its promotion history. Its patches are not deleted: they stay on their releases, but they’re on no track, so no device is offered them until you move one to a track with `shorebird patches set-track`. In a terminal, the command asks you to confirm. In scripts and CI, pass `--yes` to skip the prompt:

```
shorebird channels delete --name qa --yes
```

`--yes` needs Shorebird 1.6.129 or newer; earlier releases used `--confirm-name`, which still works but is deprecated.

In the Shorebird console, open the app’s **Tracks** page, click the delete button next to a track you added, and confirm.

The built-in `stable`, `beta`, and `staging` tracks can’t be deleted.

## Common patterns

[Staging Patches](/code-push/guides/staging-patches)Walk through the full workflow of publishing to staging, previewing, and promoting to production.

[Testing Patches with a Subset of Users](/code-push/guides/testing-patches)Route specific devices (e.g. your QA team) to a beta track using account-based logic or a hidden UI.

[Percentage-Based Rollouts](/code-push/guides/percentage-based-rollouts)Gradually roll out patches to an increasing percentage of users using tracks and a cloud key-value store.

## Frequently asked questions

### How are Shorebird tracks different from Google Play testing tracks or Apple TestFlight?

Google Play testing tracks (internal, alpha, closed testing, production) and Apple TestFlight are **app distribution** mechanisms that control which users can install a given version of your app from the store. Shorebird tracks are a **patch distribution** mechanism that controls which running devices receive a Dart code update.

Shorebird has no built-in awareness of which Play track or TestFlight group a device is in. However, you can detect the install mechanism at runtime and use that signal to subscribe the device to the appropriate Shorebird track. See [Testing Patches](/code-push/guides/testing-patches#option-3-control-shorebird-track-based-on-how-the-app-was-installed) for more details.

### Does rolling back a patch on one track affect other tracks?

Rollback applies to a patch, not to a track. Rolling back a patch removes it from every device running it, whichever track the device asks for, and each device moves to the latest patch on its track that hasn’t been rolled back, or to the base release. Devices that never received the patch are unaffected. See [Roll back a Patch](/code-push/rollback).

### Can a device be on multiple tracks at the same time?

No. A device subscribes to exactly one track per update check. If your app logic changes the track dynamically (e.g., the user opts into a beta program), it will request patches from the new track on its next update check. See [Switching a device between tracks](#switching-a-device-between-tracks) for what the device runs after it switches.

### What happens if there is no patch on the requested track?

If no patch is available on the requested track for the current release version, the device keeps running what it has: the patch it last installed, or the base release if it has none. It does **not** fall back to the `stable` track, and a device that switches to an empty track does not go back to the base release.

### Can you promote a patch to a track other than stable?

Yes. In the Shorebird console, choose **Change Track** on the patch and select any track. From the CLI, use `shorebird patches set-track`:

```
shorebird patches set-track \
  --release-version 1.0.0+1 \
  --patch-number 1 \
  --track beta
```
