> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# GitHub Actions

> Deploy a Catalyst storefront to Storefront Hosting from GitHub Actions, on merge to your default branch and as a preview for each pull request.

The Catalyst CLI runs without a browser, so you can deploy from CI instead of from a developer's machine. This page covers two workflows you can add to a Catalyst repository: one that deploys your storefront when a branch merges, and one that deploys a preview for each pull request and comments the URL on it.

Both workflows use the Catalyst CLI from your own repository, so the CLI version stays under your control and upgrades with your project.

> **Warning**
>
> The Catalyst CLI is generally available, but Storefront Hosting is currently
> in closed beta. There may be breaking changes to the Storefront Hosting APIs as we
> finalize them. To express interest in gaining access, fill out the [Storefront Hosting Closed Beta Interest Form](https://docs.google.com/forms/d/e/1FAIpQLSfDepq3qRtEtavb1wQvSvdK21pwr_kNsYQbH2B7JjWYyptUSA/viewform?usp=header).

## Prerequisites

You need the following:

* A Catalyst storefront in a GitHub repository, with `@bigcommerce/catalyst` 1.2.0 or newer installed. Check with `pnpm catalyst --version`.
* The repository already set up for Storefront Hosting, with the result committed. CI has no terminal to answer the setup prompt, so run setup locally first: scaffold with `--hosting bigcommerce`, or run `pnpm catalyst deploy` once and accept the Storefront Hosting setup prompt. Then commit the changes it makes, including `middleware.ts` and the `@opennextjs/cloudflare` dependency in `package.json`.
* A hosting project to deploy into. Create one with [`catalyst projects create`](/developer/docs/storefront/catalyst/reference/cli#projects-create), then copy its UUID from [`catalyst projects list`](/developer/docs/storefront/catalyst/reference/cli#projects-list).
* Permission to add secrets and variables to the repository.

Deploying from CI needs credentials that a browser login does not provide, so start by creating an API account for it.

## Create an API account for CI

CI has no browser to authenticate through, so unlike a local `pnpm catalyst auth login`, this account has to be created by hand. See [API Accounts](/developer/docs/overview/api-fundamentals/api-accounts) for background on store-level vs. app-level accounts before you start.

In your BigCommerce control panel, go to **Settings → API accounts → Create API account** and choose **Store API account**. Grant these scopes:

* **Infrastructure Projects** — modify
* **Infrastructure Deployments** — modify
* **Infrastructure Logs** — read-only
* **Channel Settings** — modify, if the workflow points a channel at the deployment or sets its checkout URL
* **Sites & Routes** — modify, for the same channel and checkout commands

Save the access token. You also need your storefront token, which is the same one your storefront build already uses.

> **Warning**
>
> Treat this token as a deployment credential. It can create and replace deployments on your store, so store it as a GitHub secret and never commit it.

## Understand the two sets of variables

The Catalyst CLI reads two separate sets of variables, and mixing them up is the most common cause of a failed CI deploy:

* **CLI configuration** tells the CLI which store and project to act on, and how to authenticate. In CI, supply `CATALYST_ACCESS_TOKEN` and `CATALYST_PROJECT_UUID` as environment variables. There is no `CATALYST_STORE_HASH` to set — it falls back to `BIGCOMMERCE_STORE_HASH`. These are never read from `.env` files, so putting them in `.env.local` does not authenticate CI.
* **Storefront build variables** (`BIGCOMMERCE_*` and `AUTH_SECRET`) are read by the Next.js build.

One more distinction matters: a build variable feeds the build running on the runner; it does not reach the deployed application. The deployed worker gets its own environment, which is populated only by `--secret` flags on `catalyst deploy` or by values stored with [`catalyst env add`](/developer/docs/storefront/catalyst/reference/cli#env-add). Values stored with `env add` live in `.bigcommerce/project.json`, which isn't committed, so in CI the `--secret` flags are the only way to set them. A deployment that builds successfully but returns a runtime configuration error is usually missing a `--secret`.

For the full resolution order, see [Configuration](/developer/docs/storefront/catalyst/deployment/storefront-hosting/overview#configuration).

## Deploy on merge to your default branch

In your GitHub repository, go to **Settings → Secrets and variables → Actions**. On the **Variables** tab, add `BIGCOMMERCE_STORE_HASH`, `BIGCOMMERCE_CHANNEL_ID`, and `CATALYST_PROJECT_UUID`. On the **Secrets** tab, add `BIGCOMMERCE_ACCESS_TOKEN`, `BIGCOMMERCE_STOREFRONT_TOKEN`, and `AUTH_SECRET`.

If your store offers wallet payment methods (such as PayPal) on the cart page, also add `BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN` on the **Secrets** tab, and keep the two lines that pass it in the workflow below. It's the private, `Unauthenticated`-scoped token Catalyst's [GraphQL proxy](/developer/docs/storefront/catalyst/development/middleware#with-graphql-proxy) uses for those buttons. The build doesn't need it, so a deploy without it still succeeds, but the buttons fail at runtime. If your store doesn't offer wallet payment methods, remove those two lines. See [`BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN`](/developer/docs/storefront/catalyst/reference/environment-variables#bigcommerce_storefront_unauthenticated_token) for how to create it.

Then create `.github/workflows/deploy.yml`:

**`.github/workflows/deploy.yml`**

```yaml title=".github/workflows/deploy.yml"
name: Deploy

on:
  push:
    branches: [main]

# A project serves one deployment at a time, so let one deploy finish before
# the next starts rather than racing.
concurrency:
  group: catalyst-deploy
  cancel-in-progress: false

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      # Tells the CLI which project to deploy to, and how to authenticate.
      # The store hash comes from BIGCOMMERCE_STORE_HASH below.
      CATALYST_PROJECT_UUID: ${{ vars.CATALYST_PROJECT_UUID }}
      CATALYST_ACCESS_TOKEN: ${{ secrets.BIGCOMMERCE_ACCESS_TOKEN }}
      # Read by the Next.js build on the runner.
      BIGCOMMERCE_STORE_HASH: ${{ vars.BIGCOMMERCE_STORE_HASH }}
      BIGCOMMERCE_CHANNEL_ID: ${{ vars.BIGCOMMERCE_CHANNEL_ID }}
      BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }}
      AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
      # Only needed if your store offers wallet payment methods (such as PayPal)
      # on the cart page. Runtime only: passed with --secret below.
      BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 10

      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      # The --secret flags populate the deployed application's environment.
      # The env block above only feeds the build.
      - name: Deploy
        run: |
          pnpm catalyst deploy \
            --secret "BIGCOMMERCE_STORE_HASH=$BIGCOMMERCE_STORE_HASH" \
            --secret "BIGCOMMERCE_STOREFRONT_TOKEN=$BIGCOMMERCE_STOREFRONT_TOKEN" \
            --secret "BIGCOMMERCE_CHANNEL_ID=$BIGCOMMERCE_CHANNEL_ID" \
            --secret "AUTH_SECRET=$AUTH_SECRET" \
            --secret "BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN=$BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN"
```

Keep the `--secret` flags in CI. `catalyst env add` stores values in `.bigcommerce/project.json` on the machine that runs it, and that file is gitignored, so a CI checkout never has them. Pass every variable your storefront reads at runtime the same way, including optional ones you rely on, such as `MAKESWIFT_SITE_API_KEY` for Makeswift-connected projects. A missing runtime variable doesn't fail the deploy; it fails the feature that needs it.

> **Note**
>
> If customers reach your storefront on a hostname other than the one Auth.js derives from the request, sign-in can fail host validation. Pass `--secret "AUTH_TRUST_HOST=true"` for deployments served on a hostname you control but have not configured as the canonical site URL.

## Deploy a preview for each pull request

BigCommerce publishes a GitHub Action that deploys pull request code to a hosting project and comments the URL on the pull request.

> **Preview deployments are an early version of this feature**
>
> A hosting project has one hostname, not one per deployment. A repository therefore shares **one preview project and one preview URL**, and it serves **one pull request at a time**. Deploying a preview for a second pull request replaces the first. This behavior may change as the feature develops.

### How the shared preview works

The newest open pull request deploys automatically when it receives a push. Any other open pull request claims the preview by commenting `redeploy preview` on it. When a pull request takes the preview over, the one that previously held it gets a comment explaining that the URL no longer reflects its changes, so no thread is left advertising a stale preview.

Previews do not run for pull requests from forks. The workflow builds and deploys pull request code with your store credentials in scope, so it only runs for branches in your own repository. Only users with write access can run the command.

### Create a preview project

Previews need a project of their own. Every preview overwrites the last deployment in it, so do not point previews at the project serving your storefront.

```shell
pnpm catalyst projects create
pnpm catalyst projects list
```

Copy the UUID of the project you created.

### Add configuration

In your GitHub repository, go to **Settings → Secrets and variables → Actions** and add the following. The project UUID, store hash, and channel ID are configuration rather than credentials, so add those on the **Variables** tab and the rest on the **Secrets** tab.

| Name                               | Kind     | Value                                            |
| ---------------------------------- | -------- | ------------------------------------------------ |
| `BIGCOMMERCE_PREVIEW_PROJECT_UUID` | Variable | The preview project UUID                         |
| `BIGCOMMERCE_STORE_HASH`           | Variable | Your store hash                                  |
| `BIGCOMMERCE_CHANNEL_ID`           | Variable | The channel the preview storefront serves        |
| `BIGCOMMERCE_ACCESS_TOKEN`         | Secret   | The access token from the API account above      |
| `BIGCOMMERCE_STOREFRONT_TOKEN`     | Secret   | Your storefront token                            |
| `AUTH_SECRET`                      | Secret   | Generate a new value with `openssl rand -hex 32` |

Generate an `AUTH_SECRET` for previews rather than reusing the one your storefront uses. Sessions then do not carry between a preview and your live storefront.

If `BIGCOMMERCE_PREVIEW_PROJECT_UUID` is absent, the workflow skips instead of failing, so the feature stays switched off until you configure it.

> **Note**
>
> The preview workflow passes only the access token, storefront token, and `AUTH_SECRET` to the deployment. It doesn't accept `BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN`, so if your store offers wallet payment methods on the cart page, those buttons don't work on preview deployments. Test those on a deployment that passes the token with `--secret`, as in [Deploy on merge to your default branch](#deploy-on-merge-to-your-default-branch).

### Add the workflow

Create `.github/workflows/preview-deployment.yml`:

**`.github/workflows/preview-deployment.yml`**

```yaml title=".github/workflows/preview-deployment.yml"
name: Preview Deployment

on:
  pull_request:
    types: [opened, reopened, synchronize]
  issue_comment:
    types: [created]

jobs:
  preview:
    uses: bigcommerce/catalyst/.github/workflows/deployment-preview.yml@preview-action-v1
    secrets:
      access-token: ${{ secrets.BIGCOMMERCE_ACCESS_TOKEN }}
      storefront-token: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }}
      auth-secret: ${{ secrets.AUTH_SECRET }}
```

That is the whole file. The reusable workflow sets the permissions it needs and serializes deploys across the repository, because all previews share one project.

`preview-action-v1` is a moving major tag that tracks the newest 1.x release, so fixes arrive without editing the workflow. To upgrade by hand instead, pin an exact version such as `@preview-action-v1.1.0`.

> **Warning**
>
> A job that calls a reusable workflow cannot declare `environment:`, and GitHub does not expose environment-scoped secrets to a job that does not. Keep these three secrets at repository level. To scope them to a GitHub Environment, call the action directly instead of the reusable workflow, in a job that declares the environment itself.

### Merge the workflow to your default branch

GitHub reads `issue_comment` workflows from the default branch only. Until this file is merged, commenting `redeploy preview` does nothing — including on the pull request that adds the file. Pull request previews run from the branch, but the comment command does not.

### Use the preview

Open a pull request. If it is the newest open one, it deploys and the action comments the preview URL and the commit it built.

On an older pull request, the action comments that the preview is reserved for the newest one. Comment `redeploy preview` to claim it. The command gets an eyes reaction while it runs and a rocket when it finishes, and a check named **Preview Deployment (redeploy)** appears in the pull request's checks so you can follow progress.

## Configuration reference

Pass any of these in the `with:` block of the reusable workflow.

| Input                | Default                                  | Description                                      |
| -------------------- | ---------------------------------------- | ------------------------------------------------ |
| `project-uuid`       | Reads `BIGCOMMERCE_PREVIEW_PROJECT_UUID` | Hosting project every preview deploys into       |
| `store-hash`         | Reads `BIGCOMMERCE_STORE_HASH`           | Store hash                                       |
| `channel-id`         | Reads `BIGCOMMERCE_CHANNEL_ID`           | Channel the preview storefront serves            |
| `environment`        | None                                     | GitHub Environment holding your variables        |
| `command-phrase`     | `redeploy preview`                       | Comment prefix that claims the preview           |
| `auto-deploy-newest` | `true`                                   | Set to `false` to require the command every time |
| `working-directory`  | `.`                                      | Directory holding the Catalyst project           |
| `node-version`       | `24`                                     | Node version used for the build                  |
| `pnpm-version`       | `10`                                     | The Catalyst CLI shells out to pnpm to build     |
| `api-host`           | `api.bigcommerce.com`                    | Override only for non-production stores          |
| `check-name`         | `Preview Deployment (redeploy)`          | Check row a redeploy reports against             |

The workflow also exposes outputs: `deployed` is `"true"` when the run deployed, and `url` holds the preview URL.

## Troubleshooting

| Symptom                                                                                        | Cause                                                                                 | What to try                                                                                                                     |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| The workflow reports as skipped and posts no comment                                           | `BIGCOMMERCE_PREVIEW_PROJECT_UUID` is unset, so previews are switched off             | Add the variable, and check the spelling                                                                                        |
| `Missing configuration:` naming a secret or variable                                           | The value is scoped to a GitHub Environment that the job does not declare             | Move it to repository level, or call the action directly from a job that declares the environment                               |
| `redeploy preview` does nothing at all                                                         | The workflow is not on your default branch                                            | Merge it, then comment again                                                                                                    |
| `Client configuration must include a channelId` at runtime                                     | A runtime secret did not reach the deployed application                               | Pass `BIGCOMMERCE_CHANNEL_ID` with `--secret`. Values stored with `catalyst env add` aren't available in CI.                    |
| Cart page wallet buttons fail on the deployed storefront, and `/graphql` requests are rejected | `BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN` did not reach the deployed application | Pass it with `--secret`. On preview deployments it isn't supported; see the note under [Add configuration](#add-configuration). |
| `Previews are not available for pull requests from forks`                                      | Working as intended                                                                   | Push the branch to the repository itself                                                                                        |
| `Your access token is invalid or has expired`                                                  | The store API token was revoked or expired                                            | Create a new token and update `BIGCOMMERCE_ACCESS_TOKEN`                                                                        |
| A `403` naming `store_infrastructure_projects_manage`                                          | The API account is missing a scope                                                    | Recreate it with the scopes listed above                                                                                        |
| `@bigcommerce/catalyst@<version> is older than the required 1.2.0`                             | The project's CLI is too old for the action                                           | Upgrade `@bigcommerce/catalyst` in your project                                                                                 |
| The preview URL shows another pull request's changes                                           | Another pull request claimed the shared preview                                       | Comment `redeploy preview` to claim it back                                                                                     |

To inspect a failed deployment, stream the project's logs with [`catalyst logs`](/developer/docs/storefront/catalyst/reference/cli#logs-tail).