Skip to main content
Once your overlay works locally, you want every merge to reach your testers without anyone opening the dashboard, and a tested build to reach everyone with one click. This guide sets up two workflows: one that bundles your app and site when you push to main and releases them to a private beta channel, and one you run by hand to promote what’s on beta to stable.
This guide uses the Overlayed CLI 2.0 directly; there is no separate GitHub Action. For what bundles, releases and channels are, see Understanding Bundles.

How It Fits Together

  1. overlayed bundle uploads your app and site as bundles, and the platform builds each one. With --release, it then releases them to a channel once they’re built.
  2. A release puts a bundle on a channel, and builds there: an app release builds your installer, a site release deploys your site. When the app and site are released together, the app goes first and the site’s minimum app version is set to the new app version, so users never get a site that needs an app they don’t have yet.
  3. Promoting releases the same bundle on another channel. stable gets exactly the bundle your testers ran on beta, built again for that channel.
  4. Every command fails the job when something fails. A build or release that fails, is cancelled or times out exits 1, so a broken release stops the workflow instead of passing silently.

Set It Up

1

Create a private beta channel

Every application starts with a public stable channel. Create a private channel named beta on the channels page. Your backend decides who may update from it: see Private Channels.Leave auto-release on bundle off for beta: the workflow releases to it explicitly, and overlayed bundle --release refuses a channel that already releases every bundle by itself. Check stable too: with auto-release on there, every bundle this workflow uploads would also go straight to stable, skipping beta.
2

Create an API key

Open API keys in the dashboard and create a key scoped to your application, with these permissions:
Running overlayed login on your machine opens this page with exactly these permissions preselected.
Your role must hold a permission to give it to a key. Developers can’t create or update releases, so someone whose role can, such as an application owner, needs to create this key.
3

Store it in GitHub

In your repository, open Settings → Secrets and variables → Actions and add:
  • A secret named OVERLAYED_API_KEY, holding the key.
  • A variable named OVERLAYED_APPLICATION_ID, holding your application’s id (the applicationId in your overlayed.config.ts). It isn’t secret.
The CLI reads both from the environment. The workflows below give the key only to the step that runs overlayed: npm ci and your build run code from your dependencies, which has no business seeing a key that can release.
4

Release pushes to beta

Add this workflow. It builds your project, then bundles the app and site and releases them to beta, bumping the patch version of the latest beta release each time. Runs take turns: while one releases, the newest push waits. GitHub keeps only the newest waiting run, which is all beta needs, since it releases a main that includes the pushes before it.The checkout keeps no Git credentials (persist-credentials: false) and the job’s GITHUB_TOKEN can only read: npm ci and your build run your dependencies’ code, and nothing here pushes.
.github/workflows/release-beta.yml
  • npm ci comes before bundling: the CLI reads the installed @overlayed/app and @overlayed/electron versions from node_modules, and npm run build produces the files your overlayed.config.ts includes.
  • Without a terminal the CLI doesn’t prompt, so each bundle is named on the command line. The run number and attempt keep the names unique, including when you re-run a failed job.
  • The step waits for the builds and both releases, up to 15 minutes each, and fails if any of them fails.
5

Promote beta to stable on demand

Add a second workflow that you run from the Actions tab when beta looks good. It promotes beta’s newest published app and site to stable, app first.
.github/workflows/promote-stable.yml
Both workflows share one concurrency group, so a promotion started while beta is releasing waits until that release finishes rather than promoting halfway through it. Waiting isn’t the same as succeeding: if a beta run released its app but not its site, beta’s newest site is still the previous one, so finish the site on beta before promoting. GitHub keeps one waiting run per group: if a push to main arrives while your promotion waits, it takes the promotion’s place, and you start the promotion again.Promoting needs no checkout and no overlayed.config.ts: OVERLAYED_APPLICATION_ID tells the CLI which application to work on. The version input goes through env: rather than straight into the script, so nothing typed into the form can run as a command.stable is public. In a terminal the CLI would ask before releasing there; in a workflow it goes ahead.
6

Require an approval for stable (optional)

environment: stable on its own only names a GitHub environment; GitHub creates it unprotected, and the job starts at once. To make every promotion wait for a person, open Settings → Environments, select (or create) stable, and add Required reviewers. The promote job then waits until one of them approves it.The approval gates this workflow, not the key: a key with release permissions can release to any channel of its application, including from the beta workflow. Keep the key to these workflows, and review changes to them as you would a release.

Choosing Versions

Every release needs a version, and it must be higher than the channel’s latest release of the same type.
--release-version patch (or minor, major) bumps the channel’s latest release of each type, so the app and site each count up on their own. Nothing to keep in sync, which suits a channel that gets every merge.

Rolling Out Gradually

By default a release uses its channel’s default rollout duration if the channel has one (set it on the channel), rising from 0% to 100% of users over that time. A channel without one releases to everyone at once. To choose per release, add a release option:

Using the Results

Add the global --json flag and a command prints only its result on stdout, with every id, status and dashboard link. This writes the promoted releases to the run’s summary page:
A failed command still writes its JSON before exiting 1, so the ids of whatever it did create aren’t lost. GitHub stops a step at the first failing command, so the script above turns that off (set +e) to write the summary anyway, then exits with the CLI’s code. See JSON Output.

FAQ

A command needed an answer it would normally ask for. For overlayed bundle, name each bundle (--app <name>, --site <name>) and say which to bundle; for overlayed login, don’t run it in CI at all: OVERLAYED_API_KEY replaces it.
Another run released first, or the version you passed is already behind. Use a bump (patch) or a higher version. The concurrency block in the workflows above keeps two runs from racing for the same version.
Your application either doesn’t allow releases to public channels, or requires releases there to be drafts for review. The error says which. For draft review, add --draft; the release then waits as a draft instead of going live.
The error lists the missing permission. Create a key that has it (see the table above) and update the OVERLAYED_API_KEY secret. API keys also expire: when a working workflow starts failing to authenticate, check the key’s expiry in the dashboard.
The command fails and names the app release that went out. If the site’s release was refused, failed or was cancelled, it also prints the overlayed releases create command that releases the site on its own, with the right minimum app version: fix the cause it reports, then run that command. If the wait gave up while the site was still building, the site release exists and holds its version: follow it with overlayed releases wait <id>, using the site release’s id printed above the error, instead.
overlayed releases cancel <release-id> cancels a release that is still building, or revokes one that is live. After a revoke, users fall back to the previous release on the channel.
Yes: bundle with --site alone. If the new site needs a newer app, add --min-app-version so users on older apps keep the old site. See Safe Site Releases.
The CLI finds overlayed.config.ts in the current directory or a parent, or up to three directories below it. Run the steps from your app’s package (working-directory:) if it’s deeper than that. See Config File Lookup. If the lockfile isn’t at the repository root, also point actions/setup-node at it with cache-dependency-path (e.g. apps/overlay/package-lock.json), or its npm cache step fails.
The CLI waits 15 minutes for each build or release by default, then fails. The build carries on on the server. Set OVERLAYED_WAIT_TIMEOUT (in seconds) in env: to wait longer. The limit applies to each wait, and they run one after another: bundle --release waits on the bundle builds, then the app release, then the site release, and promote --type both on the app, then the site. Keep the job’s timeout-minutes above all of them together, or GitHub can stop the job between the app and the site.

Next Steps

CLI Reference

Every command, option, exit code and JSON result.

Understanding Bundles

Bundles, channels and releases, and what each release builds.

Private Channels

Give your testers early builds.

App Updates

How installed apps pick up a new release.