A staging site is an ordinary site that builds from a different branch of the same repository. Push to main and your live site rebuilds. Push to staging and only the staging copy does. Put a password on it and nobody else sees the work in progress.

There is nothing to buy. Hosting is free, password protection is free, and you can have as many staging sites as you have branches worth testing.

Setting one up

  1. Create a new site in your dashboard and give it a name you will recognise later, such as “My Site (staging)”.
  2. Open its settings and paste the same Repository URL your live site uses. Set Branch to staging, or whatever you have called your working branch, and save. Saving generates this site’s own deploy key and its own webhook secret.
  3. Add the deploy key to your repository, alongside the one your live site already uses. GitHub: Settings → Deploy keys. GitLab: Settings → Repository → Deploy keys. Read-only access is enough.
  4. Add the webhook, again alongside the existing one, using the URL and secret shown on the staging site’s settings page. Push events only. You can restrict it to your staging branch if you would rather it stayed quiet, though it makes no difference to us: a push to a branch the site is not configured for is ignored.
  5. Turn on password protection so the staging copy stays out of search results. See Password protection.

Push to your staging branch and the build starts within seconds.

Two webhooks, two deploy keys

Every site gets its own webhook secret and its own deploy key, and a push tells us which site it is for by the secret it carries. That is why the second site needs a second webhook rather than sharing the first. It also means the two can never trigger each other by accident.

If one of the two stops building, check that its webhook is still on the repository and that the branch on the site matches the branch you are pushing.

Your own domain

A staging site takes a custom domain the same way any site does, so staging.example.com works exactly as you would expect. Add it under Domains and point the DNS record at us, as in Deploy from GitLab.

Analytics stay separate

Visits to the staging site are counted against the staging site. Your live figures are not affected by your own testing, and you do not need to remember to exclude anything.

Forms and bookings need one change

Forms and booking widgets are different. Both are identified by an id in your page markup rather than by the address the page is served from, so a staging page carrying your live ids writes to your live data: a test submission arrives in your inbox and counts against your monthly allowance, and a test booking is a real booking, holding a slot in your calendar and emailing everyone it would normally email.

Give the staging site its own form and its own booking page, then keep the id out of your markup. Per-site environment variables are made for this. Set a variable such as FORM_ID on each site, read it in your template, and one source builds both correctly:

<form action="https://api.statichost.uk/f/{{ getenv "FORM_ID" }}" method="POST">

The same applies to a booking widget’s slug.

Plus features are bought per site, so Analytics Plus, Premium Forms or Bookings Plus on your live site do not carry across. A staging site that only needs to be looked at costs nothing; one that needs a Plus feature of its own would need its own subscription. Tell us if that gets in your way and we will look at it again.

Publishing the change

Merge your staging branch into main and push. The live site rebuilds from the same commits you have been looking at. Nothing is copied between the two sites, so what you tested is what goes out.

Questions to .