UI Verify
All docs
Reference5 min readUpdated

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.

bash
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.

CommandWhat it does
uiverify uploadThe CI build. Renders the suite (or the affected subset with --only-changed), posts the pull request check, and exits on the visual verdict.
uiverify checkA 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, -hPrints 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.

FlagDefaultWhat it does
--static-dir <dir>noneUpload a built Storybook (storybook-static) or a Playwright or Vitest capture archive (uiverify-archive).
--screenshots <dir>noneUpload 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 directoryRun from this directory. Relative paths and git metadata resolve from here.
--api-url <url>UIVERIFY_API_URL, else https://uiverify.aiThe UI Verify API to talk to. Only needed for self-hosting or local development.
--strictonAn operational failure (bad key, missing bundle, network error) exits 1.
--no-strictoffAn 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.

FlagDefaultWhat it does
--only-changedoffRender 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-changesoffAccept 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-changesoffA 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.

FlagDefaultWhat it does
--target <glob>noneRepeatable. 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.

VariableRead byWhat it does
UIVERIFY_API_KEYCLIRequired. Your project key (uv_proj_...). Missing is an operational failure. Redacted from every log line.
UIVERIFY_API_URLCLIAPI URL when --api-url is not passed.
COMMIT_SHACLIThe commit the build is for. Falls back to git rev-parse HEAD.
BRANCHCLIThe branch name. Falls back to GITHUB_HEAD_REF, then BITBUCKET_BRANCH, then git's current branch.
PR_NUMBERCLIThe 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_REPOCLIThe repository as owner/repo. Falls back to GITHUB_REPOSITORY, then BITBUCKET_REPO_FULL_NAME, then the origin remote URL.
UIVERIFY_ARCHIVE_DIRPlaywright and Vitest SDKsWhere 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.

Exituploadcheck
0passed; changed with --exit-zero-on-changes; an operational failure with --no-strict; helppassed or changed; an operational failure with --no-strict; help
1changed, failed or blocked (snapshot quota reached or payment required); an operational failure (default)failed or blocked; an operational failure (default)
2A malformed invocation, even with --no-strictA 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.

Generic CI step
# 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-static

The 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.

APIWhat 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.

APIWhat 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.

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