uiverify CLI reference: upload and check flags
The uiverify CLI commands, flags, environment variables and exit codes for upload and check, plus the Playwright and Vitest capture SDK options.
uiverify is the npm package that sends a build to UI Verify. It needs Node 24 or later, runs inside a git checkout, and reads your project key from UIVERIFY_API_KEY. For a first setup, follow the quickstart for your stack (Storybook, Playwright, Vitest, screenshots); this page is the lookup table.
npx -y uiverify@1.7.0 upload --static-dir ./storybook-static
npx -y uiverify@1.7.0 check --static-dir ./storybook-static --target 'components-button--*'Commands
The CLI has two commands over the same pipeline: bundle the directory, register the build, upload it, then wait for the verdict.
| Command | What it does |
|---|---|
uiverify upload | The CI build. Renders the suite (or the affected subset with --only-changed), posts the pull request check, and exits on the visual verdict. |
uiverify check | A preview build for an agent's edit loop. Renders only what you name with --target, diffs against the real CI baseline, posts no GitHub check and never moves a CI baseline. See Check visual changes mid-edit. |
uiverify help, --help, -h | Prints the command list and exits 0. uiverify upload --help and uiverify check --help print each command's options. |
Any other command, or no command at all, prints the usage line and exits 2.
Flags shared by upload and check
Both commands take the same input and failure-handling flags. Exactly one of --static-dir or --screenshots is required.
| Flag | Default | What it does |
|---|---|---|
--static-dir <dir> | none | Upload a built Storybook (storybook-static) or a Playwright or Vitest capture archive (uiverify-archive). |
--screenshots <dir> | none | Upload a directory of finished PNG or JPEG images, diffed without rendering. Each image is keyed by its path under the directory, without the extension. |
--working-directory <dir> | current directory | Run from this directory. Relative paths and git metadata resolve from here. |
--api-url <url> | UIVERIFY_API_URL, else https://uiverify.ai | The UI Verify API to talk to. Only needed for self-hosting or local development. |
--strict | on | An operational failure (bad key, missing bundle, network error) exits 1. |
--no-strict | off | An operational failure logs a warning and exits 0. The visual verdict still gates. |
A value flag takes --flag value or --flag=value. An empty value counts as unset, so --api-url "$UNSET_VAR" still falls back to the environment and the default. If both --strict and --no-strict are passed, --strict wins. The removed --shadow and --build-cmd flags are accepted with a warning and ignored.
Upload-only flags
These flags control what a CI build renders and how its verdict gates the job. uiverify check rejects them.
| Flag | Default | What it does |
|---|---|---|
--only-changed | off | Render only the stories or tests the commit's changed files can affect and carry the rest forward. Needs a dependency graph: Storybook built with --stats-json, or a Vitest archive. Playwright archives and screenshot uploads render in full, with a warning. See Skip unchanged. |
--auto-accept-changes | off | Accept this build's changed and new snapshots as the new baseline, so the build passes. Flaky changes stay pending review. Meant for merges to your default branch. |
--exit-zero-on-changes | off | A changed verdict exits 0 and stays pending review in the dashboard. failed and blocked still exit 1. |
Check-only flags
--target picks what a preview build renders. uiverify upload rejects it.
| Flag | Default | What it does |
|---|---|---|
--target <glob> | none | Repeatable. An exact id (a story id, a Playwright or Vitest test id, a screenshot path without its extension) or an anchored glob (components-button--*, settings/*). Replaces the dependency-graph selection a CI build computes. |
--target is required for a Storybook --static-dir, because Storybook builds the whole suite; leaving it out is an operational failure. It is optional for a capture archive or --screenshots, which already hold only what you captured.
Environment variables
The CLI reads its key and build metadata from the environment. On GitHub Actions and Bitbucket Pipelines the branch, pull request and repository come from the CI's own variables, and the commit from git; the overrides win when set.
| Variable | Read by | What it does |
|---|---|---|
UIVERIFY_API_KEY | CLI | Required. Your project key (uv_proj_...). Missing is an operational failure. Redacted from every log line. |
UIVERIFY_API_URL | CLI | API URL when --api-url is not passed. |
COMMIT_SHA | CLI | The commit the build is for. Falls back to git rev-parse HEAD. |
BRANCH | CLI | The branch name. Falls back to GITHUB_HEAD_REF, then BITBUCKET_BRANCH, then git's current branch. |
PR_NUMBER | CLI | The pull request number. Falls back to GITHUB_REF (refs/pull/<n>/merge), then BITBUCKET_PR_ID. Unset off a pull request, so the build is recorded against the branch. |
UIVERIFY_REPO | CLI | The repository as owner/repo. Falls back to GITHUB_REPOSITORY, then BITBUCKET_REPO_FULL_NAME, then the origin remote URL. |
UIVERIFY_ARCHIVE_DIR | Playwright and Vitest SDKs | Where capture archives are written. Defaults to ./uiverify-archive under the working directory. |
Exit codes
uiverify exits 0, 1 or 2. Exit 1 has two independent causes: the visual verdict, and an operational failure in strict mode.
| Exit | upload | check |
|---|---|---|
| 0 | passed; changed with --exit-zero-on-changes; an operational failure with --no-strict; help | passed or changed; an operational failure with --no-strict; help |
| 1 | changed, failed or blocked (snapshot quota reached or payment required); an operational failure (default) | failed or blocked; an operational failure (default) |
| 2 | A malformed invocation, even with --no-strict | A malformed invocation, even with --no-strict |
A changed preview is the result you asked for, not a gate, so check exits 0 on it and prints the changed list for your agent to review. To make the upload verdict block merges, see Required checks.
The failed verdict means the build as a whole failed, for example because nothing could render. A story that fails to render, or fails its interaction test, inside a build that otherwise rendered turns the UI Verify pull request check red but does not change the CLI's exit code; the CLI lists it in the summary's failed count. To block merges on story failures, make the pull request check required: see Required checks.
What counts as an operational failure?
An operational failure is the run itself not happening: a missing UIVERIFY_API_KEY, a missing or empty capture directory, both --static-dir and --screenshots passed, a Storybook check without --target, or a network error, timeout or server error that survives the retries (each request is retried up to three times). The default is strict, so a dropped upload fails the job instead of leaving CI green with nothing uploaded. --no-strict logs the error and exits 0.
What counts as a malformed invocation?
A malformed invocation is a command line the CLI cannot honor: an unknown or misspelled option, a boolean given a value (--only-changed=true), a value flag with no value, a stray positional argument, an unknown command, or a flag that belongs to the other command. It exits 2 regardless of --no-strict, because --no-strict means "don't fail my job if the upload breaks", not "skip the upload", and a typo would otherwise exit 0 having uploaded nothing.
How long does upload wait for a verdict?
After uploading, the CLI polls the build every 2 seconds and prints a Rendered X / N line as the count advances. It gives up after 60 minutes, which is an operational failure. On a changed or failed verdict it prints a per-story summary and the MCP handoff for your agent. If the same commit already passed and the bundle is byte-identical, the rebuild is skipped and the CLI exits 0 straight away.
Running outside GitHub Actions
The CLI runs on any CI with Node 24+ and a git checkout. On any CI other than GitHub Actions or Bitbucket Pipelines there are no variables it knows to read, so set the branch, pull request and, if you like, the commit yourself from your CI's own variables. Many CI checkouts are a detached HEAD, where git reports the branch as HEAD, so set BRANCH explicitly.
# UIVERIFY_API_KEY comes from your CI's secret store.
# Replace the right-hand sides with your CI's own variables.
export COMMIT_SHA="$YOUR_CI_COMMIT_SHA"
export BRANCH="$YOUR_CI_BRANCH"
export PR_NUMBER="$YOUR_CI_PR_NUMBER" # leave unset when not building a pull request
export UIVERIFY_REPO="your-org/your-repo" # only if the origin remote doesn't name the repo
npx -y uiverify@1.7.0 upload --static-dir ./storybook-staticThe pull request check and comment are posted by the UI Verify GitHub App, so they need the repository on GitHub with the App connected to the project; which CI runs the upload doesn't matter. Without the App, uploads and renders still work and the CLI logs a warning. Clone with full history: in a shallow clone the CLI can't confirm which baselines are ancestors of your commit. The gh pr checks <n> --watch hint is printed only on GitHub Actions.
Playwright SDK options
@uiverify/playwright replaces @playwright/test in your imports and archives every test's final page. Its uiVerify fixture has two methods.
| API | What it does |
|---|---|
uiVerify.snapshot(name?, { delayMs? }) | Archives the page now as a named snapshot. delayMs adds extra settle time in milliseconds before the screenshot. A test that calls it is not also archived at its end. |
uiVerify.disableAutoSnapshot() | Skips the automatic end-of-test snapshot for this test. |
Archives go to ./uiverify-archive, or UIVERIFY_ARCHIVE_DIR when set. A failed test is still captured; a timed-out or interrupted one is not.
Vitest SDK options
@uiverify/vitest archives every browser-mode test's final DOM once uiverifyPlugin() (from @uiverify/vitest/plugin) is in your Vitest config. The in-test helpers are imported from @uiverify/vitest.
| API | What it does |
|---|---|
uiverifyPlugin({ outDir }) | Where archives are written. Defaults to UIVERIFY_ARCHIVE_DIR, else ./uiverify-archive. |
uiverifyPlugin({ disableAutoSnapshot: true }) | No automatic end-of-test snapshots for the whole suite; only takeSnapshot() calls capture. Default false. |
await takeSnapshot(name?, { baselineImage? }) | Archives the DOM now as a named snapshot and replaces the test's automatic one. baselineImage is a design PNG to compare against until a render is accepted. See Design baselines. |
disableAutoSnapshot() | Skips the automatic snapshot for the current test only. |
await preloadFonts() | Loads every registered font before you render, for components that measure text on mount. |
A second takeSnapshot() with the same name in one test is saved as name #2 rather than replacing the first, and takeSnapshot("", { baselineImage }) keeps the automatic snapshot's id. Each run clears the previous run's archive before writing.
Set up visual testing
From no visual tests to a green check on every pull request.
Visual testing for agents
UI Verify captures your UI on every pull request and an AI judge tells an intended change from a real regression. See how it works.
Get started