Getting started

Install the App, add one step to CI, open a pull request. There is no baseline to record first, no screenshots to approve, and nothing to install in your test runner.

1. Install the GitHub App

Sign in with GitHub and install the App on the repositories it should watch. Straight after installation you land on a page that shows the workflow below.

2. Upload your Storybook build from CI

Add a workflow that builds Storybook and uploads it. Run it on pull requests and on pushes to your default branch: those default-branch builds are what baselines resolve to, so without them every pull request looks like a first run.

.github/workflows/merrykat.yml
name: merrykat

on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: merrykat-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  visual:
    runs-on: ubuntu-latest
    permissions:
      contents: read # lets actions/checkout clone the repo
      id-token: write # lets the CLI authenticate with no configured secret
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm run build-storybook

      - name: Upload the build to Merrykat
        run: npx merrykat upload --dir ./storybook-static

The uploader packs the build and sends it; your CI does no rendering and needs no browser. Authentication comes from the job’s own OIDC token, which is why id-token: write is in the permissions block and why there is no secret to configure.

A note on actions/checkout settings

ref: ${{ github.event.pull_request.head.sha }} builds the pull request’s own head commit. On pull_request runs, checkout otherwise gives you GitHub’s ephemeral merge of your branch into its base which can lead to confusing results.

fetch-depth: 0 gives the CLI the history it needs. Baseline resolution and the pull request’s diff both come from local git merge-base and git diff.

3. Read the first run

The first pull request after installation has no baseline to compare against, so nothing can be diffed. Instead of a dead end you get an inventory: how many stories there are, which of them failed to render or rendered nothing, and a gallery of sampled renders, chosen for breadth across components, plus anything that looks unusual, each judged on whether the screenshot plausibly depicts the story it came from.

From the second pull request onwards you get comparisons with the baseline: one sticky comment and one check run, updated in place as the run progresses, with what changed, what broke, what is new, and a verdict on each examined difference.

Several Storybooks in one repository

Upload each one under its own project key. Builds and runs are identified by project, so two Storybooks in a monorepo never resolve to each other’s baselines.

- run: npx merrykat upload --dir ./packages/design-system/storybook-static --project design-system
- run: npx merrykat upload --dir ./apps/admin/storybook-static --project admin

Next

The defaults are a desktop and a mobile viewport with the accessibility pass on, which is a reasonable place to stay. When you want to change it, everything lives in one file — see Configuration. If your builds are large, the biggest win available is not rendering the stories that cannot have changed; see Fingerprinting.

NextConfiguration