Browse the docs

GitHub Actions

Every option of the QualityGate action, with complete workflows for keyless and API key setups.

The QualityGate action is qualitygate/scan-action. It runs on GitHub-hosted and self-hosted Linux runners and does not need a checkout of your repository.

Connect the repository in the app under Projects → Connect GitHub. The workflow then needs no QualityGate secret at all:

name: QualityGate

on:
  pull_request:

permissions:
  contents: read
  id-token: write

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - name: Install the scanning browser
        run: npx -y playwright@1.61.1 install --with-deps chromium

      - uses: qualitygate/scan-action@v1
        with:
          url: https://preview-${{ github.event.pull_request.number }}.example.com

id-token: write lets the runner request a token that GitHub signs, naming the repository, branch, and workflow. QualityGate matches it to your project by GitHub’s numeric repository id, which survives renames and is never reused. Each run also asks GitHub whether the App is still installed, so uninstalling the App stops keyless runs immediately. The App posts the pull request comment, so the workflow needs neither pull-requests: write nor a GITHUB_TOKEN.

Setup with an API key

Use an API key when you cannot install the GitHub App, or for GitLab and Bitbucket. Create the project in the app, create a key on the project card, and store it as the repository secret QUALITYGATE_API_KEY (Settings → Secrets and variables → Actions).

name: QualityGate

on:
  pull_request:

permissions:
  contents: read
  pull-requests: write
  id-token: write

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - name: Install the scanning browser
        run: npx -y playwright@1.61.1 install --with-deps chromium

      - uses: qualitygate/scan-action@v1
        env:
          GITHUB_TOKEN: ${{ github.token }}
        with:
          api_key: ${{ secrets.QUALITYGATE_API_KEY }}
          url: ${{ vars.QUALITYGATE_PREVIEW_URL }}
          bundle: a11y
          fail_on: critical=0,serious=0

Without the GitHub App, the action posts the comment itself as github-actions[bot], which is why this version grants pull-requests: write and passes GITHUB_TOKEN. id-token: write is optional here but recommended: it sends proof of the repository along with the key. See Repository identity.

Inputs

InputDefaultDescription
urlrequiredHTTP or HTTPS address to scan. Its host must be one of the project’s allowed domains.
api_keynoneProject API key. Required unless the repository is connected through the GitHub App and id-token: write is granted.
bundlea11yWhich checks to run. See Bundles and checks.
fail_oncritical=0,serious=0Limits for the gate, or regression. See Gates and regression mode.
wait_for_urltruePoll the URL until it returns a 2xx status before scanning.
wait_timeout120Seconds to wait for the URL. A positive whole number.
commenttrueCreate or update the QualityGate comment on the pull request.

Pin @v1 to receive compatible fixes automatically, or pin an exact release such as @v1.0.0 if your organization requires immutable references.

Permissions

PermissionNeeded for
contents: readThe default for most workflows. QualityGate does not read your code.
id-token: writeKeyless runs, and proof of repository identity for key-based runs.
pull-requests: writeOnly when the action posts its own comment, that is, without the GitHub App.

Supported triggers

EventWhat the run does
pull_requestScans the preview, comments on the pull request, and gates the check.
pushOn the project’s default branch, records the baseline that regression mode compares against. No comment.
scheduleA scheduled scan with its own monthly allowance. No comment, never a baseline.

Other events, including workflow_dispatch and deployment_status, are rejected. pull_request_target is deliberately unsupported: it runs with write access and secrets in the context of untrusted fork code.

Using a preview URL from another job

If a job in the same workflow deploys the preview, expose its URL as an output and make the scan depend on it:

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      url: ${{ steps.deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v5
      - id: deploy
        run: echo "url=$(./scripts/deploy-preview.sh)" >> "$GITHUB_OUTPUT"

  quality:
    needs: deploy
    runs-on: ubuntu-latest
    steps:
      - run: npx -y playwright@1.61.1 install --with-deps chromium
      - uses: qualitygate/scan-action@v1
        with:
          url: ${{ needs.deploy.outputs.url }}

Ready-made versions for Vercel, Netlify, Render, and Cloudflare Pages are in Preview deployments.

The pull request comment

QualityGate keeps exactly one comment per pull request and updates it on every run, so a busy pull request does not fill up with reports. With the GitHub App installed, the comment comes from the QualityGate App with its own avatar. Without it, the action falls back to github-actions[bot] using the workflow’s token.

A comment that cannot be posted is logged as a warning and never changes the result. The full report is always written to the job summary as well. Set comment: 'false' to skip the comment and keep only the job summary.

Advisory mode that never blocks

To try QualityGate without blocking anyone, let the step fail without failing the job:

- run: npx -y playwright@1.61.1 install --with-deps chromium
- uses: qualitygate/scan-action@v1
  continue-on-error: true
  with:
    url: ${{ vars.QUALITYGATE_PREVIEW_URL }}

The comment and job summary still show the real verdict; GitHub simply shows the job as passed. Remove continue-on-error when you are ready to enforce.

Fork pull requests

GitHub gives workflows triggered by forks no secrets, a read-only token, and no OIDC token. A scan from a fork therefore cannot authorize and fails closed. Run QualityGate on branches in your own repository, or re-run the change from a branch once it has been reviewed. Do not switch to pull_request_target to work around this.

Repository identity

An API key alone says which project is asking, but not from which repository. Granting id-token: write adds a GitHub-signed statement of the repository, which QualityGate checks against the project. It costs nothing: the token is scoped to QualityGate, expires in minutes, and grants no access to your repository.

Once every workflow for a project sends it, contact us to switch the project to require it. From then on, a copied key used from any other repository is refused.

Self-hosted runners

Self-hosted runners work if they run Linux on a runner version that supports node24 actions, can install Chromium with Playwright, and can reach both your preview and https://api.qualitygate.dev. Drop --with-deps from the install step if the runner cannot use sudo apt-get, and install Chromium’s system libraries in the runner image instead.