# Stage and share (/docs/build/staging)



Staging is a private copy of your hosted app on test data, at one address that never changes: `https://<project-slug>--staging.domino.page`. Use it to show your team or a client what's coming before it goes live. It stays online when nobody is working on the app, unlike a [preview from your own computer](/docs/build/development).

* **Members don't see it.** By default only people with access to the project in Console can open staging. You can add a password for reviewers outside Console.
* **It is what goes live.** A [promotion](/docs/concepts/promotion) carries the build serving staging to your live app.
* **What you can do in Console.** Stage a [Playground](/docs/build/playground) checkpoint with **Stage**, set a password under **Settings → Staging access**, follow builds under **Deployments**, and try quests as a test member with the test toolbar.

Each staging build comes from an exact commit pushed to your project's repository. `domino stage` builds it in Domino's isolated builder and, when the build succeeds, makes staging serve it. A failed build changes nothing: the previous one keeps serving.

```mermaid
flowchart LR
    Push["git push"] --> Stage["domino stage"]
    Stage --> Build["Install, check, build, pack"]
    Build --> Deploy["Deploy and activate"]
    Deploy --> URL["Staging URL"]
    Build -. "fails" .-> Previous["Previous build keeps serving"]
```

> [!NOTE] Staging needs a pushed commit
> Staging requires a hosted repository for the selected project and a pushed commit. A working connected development preview does not create that repository. Member sign-in diagnostics are separate from staging readiness; missing sign-in configuration does not prevent an app build.

## Stage a commit [#stage-a-commit]

### 1. Integrate, check, and push [#1-integrate-check-and-push]

For Domino-hosted projects, use `main` for the team's accepted source and a work branch for each change. Fetch the latest `main`, preserve your existing work, and reconcile any newer team changes. Check and build the integrated result, merge through your repository's review process, and push to `main` before updating shared staging.

When resuming Playground or a local checkout, compare it with remote `main` and the current staging commit from `domino builds --json`. If previously staged work is absent from `main`, reconcile the histories first. Follow the [source and release workflow](/docs/reference/hosted#source-and-release-workflow) for the complete procedure.

This is a workflow convention, not an enforced CLI restriction. `domino stage` builds a pushed commit; it does not commit, push, merge branches, or update Playground. Branch names do not select environments. Development and staging currently share test data and behavior definitions.

### 2. Stage the pushed commit [#2-stage-the-pushed-commit]

```sh
pnpm exec domino stage --commit <sha> --json
```

In a terminal, `domino stage` shows the target project, test environment, API URL, and full commit SHA before submitting the build. Press Enter to cancel without starting a build. Use `--yes` to skip confirmation, or keep `--json` for scripts.

Use the SHA of the integrated commit pushed to `main`. A short SHA works: stage resolves it to the full commit in your checkout and fails with Git's reason when it is unknown or ambiguous. A full SHA is sent as given, so your checkout does not need it. Without `--commit`, stage defaults to local `HEAD` and requires a clean working tree. If `main` advances before submission, reconcile and validate the newer result. Stage builds the exact pushed commit: a commit the project's repository doesn't have, mistyped or not pushed yet, is refused before any build starts (`REJECTED`, `apiCode: "commit-not-found"`). Push it, then stage again. If a submission loses its response, retry with the original full commit SHA and request ID shown by the CLI.

Before submitting, stage warns about what the staged app will lack, without blocking the build: required [app secrets](/docs/build/hosted-app#set-app-secrets) that are not set, and [wallet sign-in](/docs/build/wallets#sign-in-with-a-wallet) that is off in test while the app signs members in with a wallet, and [profiles](/docs/build/profiles#turn-profiles-on) that leave every leaderboard row with a generated name such as `Participant 1a2b3c4d`. Each warning names the command that fixes it. With `--json`, the result lists missing secrets in `missingSecrets` and the wallet sign-in and leaderboard name warnings in `warnings` (`wallet-sign-in-off`, `generated-leaderboard-names`).

Once the build is ready, stage also prints the [deployment warnings](/docs/build/publishing#1-validate-the-remote-deployment) of the quests and other resources it published to test, and `--json` adds them to `warnings`. For example, quests whose files you deleted stay published until you [archive them](/docs/build/publishing#archive-a-quest), and stage names them under `quests-not-in-bundle`.

### 3. Follow the build [#3-follow-the-build]

```sh
pnpm exec domino builds --json
pnpm exec domino logs BUILD_ID --json
```

* **Wait or not.** `domino stage` waits for the build and fails with `STAGE_BUILD_FAILED` if it fails. `--no-wait` returns the build ID right after submission.
* **Pick a build.** Run `domino logs` to select a recent build from its status, time, commit, and ID. Explicit `domino logs BUILD_ID` skips the build picker.
* **Pick a project.** Without a configured project, `builds` and `logs` offer a project picker first.
* **Output.** Logs print as text by default; `--json` returns the build ID and log text together.

### 4. Share the staging URL [#4-share-the-staging-url]

Staged builds use `https://<project-slug>--staging.domino.page`. The address stays the same across builds. Addresses from before the move to `domino.page`, `https://staging-<project-slug>.domino.run`, no longer serve: they answer `410 Gone` with a page naming the new address, and do not redirect.

Share the URL returned by the successful build. A failed build leaves the previous preview active. Staging uses test data and remains online after local development stops. Without `--json`, `domino stage` prints the URL with a reminder of who can open it.

> [!WARNING] Don't print staging links
> Staging is for review: only visitors with staging access can open it, and a slug change moves its address. For QR codes, stickers, and anything else you print or publish, use the [live app's address](/docs/concepts/hosting#what-domino-hosts) or its [custom domain](/docs/concepts/hosting#custom-domains), and route short paths such as `/r/<id>` inside your app.

## Control who can open staging [#control-who-can-open-staging]

Staging is never public. By default, visitors sign in with Domino Console and need access to the project in **Test**. Console's **Open app** links sign you in directly. The same holds for the app's member API: requests that name the staging address answer only for browsers that signed in to staging and call through the staging address, so a script calling the API directly with the staging origin gets `staging-access-required`.

To share with reviewers outside Console, open **Settings → Staging access**, choose **Password**, and save a password of at least 8 characters. Visitors then enter it on the staging page; Console members can still sign in instead.

| Staging access                | Who can open staging                            | Ends when                                                                  |
| ----------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------- |
| **Console sign-in** (default) | People with read access to the project in Test. | The session expires after 12 hours.                                        |
| **Password**                  | Anyone with the password, plus Console members. | The session expires after 12 hours, or the password changes or is removed. |

> [!DEV] Set the staging password from the CLI
> `PUT /management/v1/staging/access` sets the same policy as Console and needs `publish`: write `{"mode":"password","password":"<8-128 characters>"}` to a file such as `access.json`, which keeps the password out of shell history, and run `domino api PUT /staging/access access.json --environment test`. A body of `{"mode":"console"}` returns to Console sign-in only, and `domino api GET /staging/access --environment test` reads the current mode.

Changing staging access requires publish permission. Password attempts are limited to 10 per IP address (an IPv6 address counts by its /64) and 100 per project in 5 minutes, right or wrong; past that, attempts are refused for a few minutes. A staging session belongs to the address that issued it and does not carry over to another one, such as after a slug change.

### Try quests with the test toolbar [#try-quests-with-the-test-toolbar]

People who signed in with Console and hold publish permission see the [test toolbar](/docs/use/quests#try-quests-on-your-staging-site) on staging. With it you can act as a test member, move them to the next day, pass their quests, follow their activity, send tracker records, and link wallets and X or Discord accounts to them. Everyone else sees nothing new, including visitors who signed in with the staging password.

Your coding agent can use the toolbar too, to check the app's screens as a test member. It opens staging with a one-time staging link minted by your `domino login` session, your API key, or an organization automation key, and acts as you, or as the automation key.

> [!DEV] Staging links for agents
> `POST /management/v1/staging/link` (`domino api POST /staging/link body.json` with `{ "path": "/" }`) returns a URL that opens staging for one minute. The link names an operator when a Console or CLI session minted it, or an API key or organization automation key that itself holds `publish`. On every toolbar request, Domino checks again that the operator holds `publish` in **Test** and that the key behind the link is still active, so revoking the key ends the toolbar at once. Toolbar actions are audited as the person or as `automation:<id>`. Hosted app keys, service keys, and Claude or ChatGPT connections get a link that only views staging. With `preview`, the same route opens a running `domino dev` preview instead ([Act as a test member on the preview](/docs/build/development#act-as-a-test-member-on-the-preview)). The agent steps are under "Check participant flows on staging" in the Domino skill's campaign testing reference (`references/campaign-testing.md`).

> [!DEV] Keep bottom bars clear of the toolbar
> The toolbar's collapsed bar sits in the bottom-right corner, over your app. While it is on the page, `<html>` carries the `data-domino-staging-toolbar` attribute and the `--domino-staging-toolbar-inset-bottom` custom property: how far the collapsed bar reaches up from the bottom of the screen, in px. It follows the bar's size and the screen's, and opening the toolbar doesn't change it. **Hide toolbar** in the toolbar's `⋯` menu hides it for the browser tab, leaving a small square marker in the corner to bring it back; while it is hidden, the property is `0px` and the attribute is gone, so the page lays out as on live. Use the property to lift a fixed bottom bar, such as a phone tab bar, above the toolbar. Live has no toolbar, so the `0px` fallback applies there:
>
> ```css
> nav.tab-bar {
>   padding-bottom: calc(env(safe-area-inset-bottom) + var(--domino-staging-toolbar-inset-bottom, 0px));
> }
> ```

> [!DEV] Reserved paths
> Staging reserves the `/__domino/staging-access` path for sign-in and removes its session cookie before requests reach your app. It also adds a script, `/__domino/toolbar.js`, to every HTML page it serves, and reserves that script and the `/__domino/toolbar/` paths it calls, and `/__domino/environment`, which tells your app it is running on staging ([Tell test from live](/docs/build/hosted-app#tell-test-from-live)).

## What a build does [#what-a-build-does]

Each build runs in a fresh, isolated sandbox with no Domino or Cloudflare credentials:

| Phase    | What happens                                                                                                                                                                                                                    |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Install  | `pnpm install --frozen-lockfile`; `packageManager` must pin an exact pnpm 10 version, such as `pnpm@10.18.0`                                                                                                                    |
| Check    | Your `checkScript`                                                                                                                                                                                                              |
| Build    | Your `buildScript`, which emits the app and the `.domino-build` resource artifacts                                                                                                                                              |
| Pack     | Collects the output, enforces the [output limits](/docs/build/hosted-app#build-outputs), and stages your quests and catalog                                                                                                     |
| Deploy   | Binds [secrets](/docs/build/hosted-app#set-app-secrets), the [management key](/docs/build/hosted-app#call-the-management-api-from-your-server), and [storage](/docs/build/storage), applies SQL migrations, and uploads the app |
| Activate | Switches staging to the new build and activates its quests, catalog, trackers, engagement, leaderboards, and referrals in test                                                                                                  |

* **Build environment.** Scripts see only `CI=true`. Live serves the same build, so nothing in the build says test or live; check at runtime instead ([Tell test from live](/docs/build/hosted-app#tell-test-from-live)). App secrets, the management key, and storage reach the deployed Worker, never the build.
* **Time limits.** Install has 5 minutes, and check, build, and pack 3 minutes each. The whole build step has 15 minutes and is retried once. The pushed source may be at most 20 MB.
* **One build at a time.** A project accepts a new build only after the running one finishes, or after 30 minutes.
* **All or nothing.** A failed build changes nothing that serves: the previous build, its routing, and its resources stay active. SQL migrations are the exception; see [Write migrations](/docs/build/storage#write-migrations).
* **Immutable.** Every build is fixed at its commit. Changing secrets, settings, or `relay.json` takes effect on the next stage.

## Follow builds in Console [#follow-builds-in-console]

Select your project and the **Test** environment, then open **Deployments** under Developers. The page shows the latest 30 hosted app builds, including failed attempts.

| Element                                      | What it shows or does                                                                                                                                                                                                   |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **All**, **Ready**, **Building**, **Failed** | Filter the list by build status.                                                                                                                                                                                        |
| Screenshot                                   | A capture of the app after a successful build, when one could be taken.                                                                                                                                                 |
| **Current**                                  | Marks the build actually serving your test app, even when a newer build fails.                                                                                                                                          |
| A build                                      | Its commit message when available, short commit SHA, publisher, creation time, total duration, and build steps. Progress and logs update automatically while commands run. Failed builds open their logs automatically. |
| **Copy**                                     | Copies the build output for troubleshooting. Logs retain the latest 500,000 characters, including automatic build retries. **Find in logs** searches them.                                                              |
| **Open app**                                 | Opens the current test app. Older builds do not have separate preview links.                                                                                                                                            |
| **Reconnect**                                | Appears if updates disconnect. Console keeps the last received data visible.                                                                                                                                            |

Creating a project from a starter usually stages its first build automatically: the untouched starter, with its sample resources in test. If no build appears, run `domino stage`. Start every later build with `domino stage`. Console does not retry, cancel, roll back, or publish apps to production. Quest Studio publications are separate from this hosted app build history.

## Where next [#where-next]

- [Configure a hosted app](/docs/build/hosted-app) — app settings, routing, secrets, and build outputs.
- [Publish changes and go live](/docs/build/publishing) — publish quests and other resources to test and live.
- [Troubleshooting](/docs/reference/troubleshooting) — build and configuration failures.
