---
title: "White Labeling"
description: "Patch many branded store apps built from the same Flutter code, from one CI job"
---

White labeling means publishing the same app to the stores many times, once per customer, each with its own name, icon, and bundle ID. With Shorebird, you fix a bug in the shared Dart code once and one CI job patches every brand’s app. Your agreements with the app stores still apply; see [Store compliance](/code-push/faq/#store-compliance).

The setup is:

1. Give each store app its own Shorebird app.
2. Create a service account for CI.
3. Run `shorebird init` once in each app.
4. Release each app with `shorebird release`.
5. Patch every app from one CI job, trying each fix on a few apps first.

## Give each store app its own Shorebird app

Each app in the stores needs its own Shorebird app, with its own app ID. One Shorebird app covers both Android and iOS for that store app, so a fleet of 100 brands on both stores is 100 Shorebird apps.

Two brands can’t share a Shorebird app because a patch belongs to one release. Shorebird matches patches to devices by app ID and release version, and a release holds one build per platform. A patch is built against that build and installs only on it. Separate apps also give each brand its own releases, tracks, rollbacks, and insights, so you can roll back a patch for one brand without touching the others.

How you get one app per brand depends on how your project is laid out:

* **One Flutter project per brand** (for example, host apps generated from a template that share the Dart code as a package): run `shorebird init` in each project. Each gets its own `shorebird.yaml`.
* **One Flutter project with a flavor per brand**: `shorebird init` creates one Shorebird app per flavor and lists them under `flavors:` in `shorebird.yaml`. Pass `--flavor` to `shorebird release` and `shorebird patch`. See [Apps with flavors](/code-push/guides/flavors/).

## Create a service account for CI

Give your pipeline a [service account](/account/service-accounts/) rather than a person’s key, so it keeps working when people change roles or leave. Pick its role by what the pipeline does:

* **App Manager** if CI runs `shorebird init`, which creates apps.
* **Developer** if CI only creates releases and patches.

Create a key for the account and store it in your CI’s secrets as `SHOREBIRD_TOKEN`. The Shorebird CLI reads it from that environment variable. See [Use a key from CI or a script](/account/service-accounts/#use-a-key-from-ci-or-a-script).

## Run `shorebird init` once in each app

Run `shorebird init` in each brand’s project, for example as a step of the template that generates a new brand, then commit the `shorebird.yaml` it creates and the `pubspec.yaml` change:

```
cd apps/brand_a
shorebird init --org-id=<org id> --display-name="Brand A"
```

In CI, `shorebird init` doesn’t ask questions. Without `--display-name` it names the app after the `name` in `pubspec.yaml`, which is often the same for every brand, and without `--org-id` it fails if the account can create apps in more than one organization. See [Non-interactive mode](/code-push/ci/scripting/#non-interactive-mode).

Run it once per app. If `shorebird.yaml` already exists, `shorebird init` stops. Don’t pass `--force` to an app you’ve already released: it creates a new Shorebird app with a new app ID, and builds already in the stores won’t receive patches published to it.

With flavors, add a brand by adding its flavor and running `shorebird init` again without `--force`. It creates apps for the new flavors and keeps the existing ones. It names each new app after the existing app with the flavor in parentheses, and ignores `--display-name`, so rename it in the console if you need to. It also rewrites `shorebird.yaml` with only `app_id` and `flavors`, so check the diff and put back any other settings, such as `auto_update: false`, before you commit.

## Release each app

Every build you upload to the stores must be the one `shorebird release` made. Release each brand’s app for both platforms in one command:

```
cd apps/brand_a
shorebird release --platforms=android,ios
```

With flavors, run it once per flavor with `--flavor=brand_a`. Releasing and patching for iOS need a Mac. Upload the builds the command produces; a build of the same version made any other way can’t install patches (see [Patches can’t install because the app isn’t the build Shorebird released](/code-push/troubleshooting/#patches-cant-install-because-the-app-isnt-the-build-shorebird-released)). See [Releasing](/code-push/release/) for the details of each platform.

## Patch every app from one CI job

Once each app has a release in the stores, a fix to the shared Dart code is a loop over the apps. This script patches each one and lists any that failed at the end, rather than stopping at the first failure:

```
#!/usr/bin/env bash
# Needs SHOREBIRD_TOKEN set to the service account's key.
failed=()
for app in apps/*/; do
  if ! (cd "$app" && shorebird patch --platforms=android,ios \
      --release-version=latest --track=stable); then
    failed+=("$app")
  fi
done
if [ ${#failed[@]} -gt 0 ]; then
  echo "Patching failed for: ${failed[*]}"
  exit 1
fi
```

With flavors, loop over the flavor names instead and pass `--flavor "$flavor"` from the project’s root. The iOS patches need a macOS runner; on a Linux runner, pass `--platforms=android` and patch iOS from a macOS job.

`--release-version=latest` patches each app’s most recently updated release for each platform, so Android and iOS can land on different versions. If a brand has more than one release in use, patch each version with `--release-version=<version>`. Each patch is a build, so a large fleet takes a while; split the apps across parallel CI jobs to shorten it.

If a patch changes native code or assets, `shorebird patch` stops in CI. See [Patch stops in CI with `Not publishing`](/code-push/troubleshooting/#patch-stops-in-ci-with-not-publishing-exit-code-70).

## Try each fix on a few apps first

Before a fix reaches the whole fleet, try it on your flagship app and two or three other brands:

1. Run the loop over those apps with `--track=staging`. Devices check the `stable` track by default, so users don’t get these patches.
2. Test each patch with `shorebird preview --track=staging`, or on a device [subscribed to the staging track](/code-push/tracks/#subscribing-a-device-to-a-track).
3. Promote the patches to `stable` in the console, or with `shorebird patches promote --release-version=<version> --patch-number=<n>` in each app’s directory (with flavors, add `--flavor=<brand>`). Android and iOS each get their own patch, with its own number and possibly a different release version, so promote each platform’s patch. `shorebird patch --json` prints the `release_version` and `patch_number` of each. See [Promoting a patch between tracks](/code-push/tracks/#promoting-a-patch-between-tracks).
4. Run the loop over the remaining apps with `--track=stable`.

See [Staging Patches](/code-push/guides/staging-patches/) for a walkthrough of the staging track.
