Skip to content

SDK Reference

The @pxdiff/sdk package provides programmatic access to the pxdiff API for screenshot capture, diffing, and approval workflows.

Terminal window
npm install @pxdiff/sdk

The main class for interacting with pxdiff.

import { PxdiffClient } from "@pxdiff/sdk";
const client = new PxdiffClient({
apiKey: "pxd_your_key_here",
apiUrl: "https://pxdiff.com", // optional, this is the default
project: "my-app", // optional project slug
maxRetries: 3, // optional, retry on 429/5xx
retryBaseDelayMs: 1000, // optional, base delay for backoff
});
OptionTypeDefaultDescription
apiKeystring(required)API key (format: pxd_ prefix + base64url)
apiUrlstring"https://pxdiff.com"API base URL
projectstringProject slug for scoping operations
maxRetriesnumber3Max retry attempts on 429 and 5xx. Set to 0 to disable.
retryBaseDelayMsnumber1000Base delay in ms for exponential backoff

Creates a new capture. Supports two modes:

  • Fleet mode — provide URLs, pxdiff captures the screenshots for you
  • Manual mode — provide PNG buffers directly
// Fleet mode: pxdiff captures the screenshots
const capture = await client.createCapture({
suite: "storybook",
branch: "feat/login",
commit: "abc123",
targets: [
{ name: "button-primary", url: "https://storybook.example.com/iframe.html?id=button--primary" },
{ name: "button-secondary", url: "https://storybook.example.com/iframe.html?id=button--secondary" },
],
});
// Manual mode: upload your own screenshots
const capture = await client.createCapture({
suite: "e2e",
branch: "feat/login",
commit: "abc123",
snapshots: [
{ name: "login-page", png: screenshotBuffer },
{ name: "signup-page", png: signupBuffer, viewport: { width: 1280, height: 900 } },
],
});

Options:

FieldTypeDefaultDescription
suitestring(required)Suite identifier
branchstringGit branch name
commitstringGit commit SHA
baselineRefstringBaseline branch for comparison
autoApprovebooleanAuto-approve diffs
ephemeralbooleanNon-persistent capture (no branch/commit)
isLocalbooleanLocal development capture
snapshotsSnapshotInput[]PNG buffers (manual mode)
targetsCaptureTarget[]URLs to capture (fleet mode)

Returns: Capture


Fetch full details of a capture including all snapshots.

const capture = await client.getCapture("cap_abc123");
console.log(capture.status); // "completed"
console.log(capture.snapshots.length); // 12

Returns: Capture


Search for a capture by commit, branch, or suite.

const capture = await client.findCapture({
suite: "storybook",
commits: ["abc123def456"],
});
FieldTypeDescription
suitestringSuite identifier
commitsstring[]Commit SHAs to search
branchstringGit branch name

Returns: Capture or null


uploadSnapshot(captureId, name, png, options?)

Section titled “uploadSnapshot(captureId, name, png, options?)”

Upload a single screenshot to an existing capture.

const snapshot = await client.uploadSnapshot("cap_abc", "login-page", pngBuffer, {
viewport: { width: 1280, height: 720 },
});

Returns: Snapshot


Upload a screenshot and immediately get diff results. Used by the Playwright plugin for per-screenshot assertions.

const result = await client.uploadSnapshotWithDiff({
name: "homepage",
png: screenshotBuffer,
suite: "e2e",
branch: "feat/redesign",
commit: "abc123",
baselineRef: "main",
viewport: { width: 1280, height: 720 },
sessionId: "session_xyz",
});
console.log(result.status); // "changed"
console.log(result.diffPercentage); // 2.4
FieldTypeDefaultDescription
namestring(required)Snapshot identifier
pngBuffer | Uint8Array(required)Image buffer
suitestring(required)Suite identifier
branchstring(required)Git branch
commitstring(required)Git commit SHA
baselineRefstring(required)Baseline reference
viewportobject{ width, height }
sessionIdstringSession ID for grouping
pathstring[]Display hierarchy path
metadataRecordCustom metadata
isLocalbooleanLocal development
autoApprovebooleanAuto-approve this snapshot

Returns: SnapshotDiffResult


Create a diff comparing a head capture against a baseline.

const diff = await client.createDiff({
suite: "storybook",
headCaptureId: "cap_abc",
baselineRef: "main",
threshold: 0.063,
});
FieldTypeDefaultDescription
suitestring(required)Suite identifier
headCaptureIdstring(required)Capture to compare
baselineRefstringBaseline branch reference
baseCaptureIdstringSpecific capture to compare against
thresholdnumber0.063Pixel difference threshold (0–1)
antiAliasingbooleanIgnore anti-aliasing differences
manifeststring[]Only diff these snapshot names
targetRefsstring[]Target branches for approval

Returns: Diff


Fetch full diff details including per-snapshot results.

const diff = await client.getDiff("diff_abc");
for (const result of diff.results) {
if (result.status === "changed") {
console.log(`${result.name}: ${result.diffPercentage}% different`);
}
}

Returns: Diff


Trigger a re-diff against current baselines. Useful after baselines have been updated.

Returns: Diff


Approve snapshots in a diff, promoting them to baselines.

// Approve specific snapshots
await client.approve("diff_abc", {
snapshots: ["button-primary", "header"],
});
// Approve all
await client.approve("diff_abc");
FieldTypeDescription
snapshotsstring[]Specific snapshot names. Omit to approve all.

Returns: { approved: number, total: number }


Revoke approval for snapshots, resetting them to pending.

// Revoke specific
await client.revoke("diff_abc", ["button-primary"]);
// Revoke all
await client.revoke("diff_abc");

Returns: { revoked: number }


List all branch refs that have approved baselines for a suite.

const refs = await client.getBaselineRefs("storybook");
// ["main", "develop"]

Upload a tar.gz archive of a static site (e.g. Storybook build) for story discovery.

const site = await client.uploadSite(archiveBuffer);
console.log(site.id); // "site_abc"
console.log(site.status); // "pending"

Returns: Site


Discover stories from an uploaded Storybook site.

const stories = await client.discoverStories("site_abc");
for (const story of stories) {
console.log(story.title, story.name, story.url);
}

Returns: StoryEntry[]


Sessions group inline screenshot uploads into a single capture and diff. Used by the Playwright plugin.

const session = await client.createSession({
suite: "playwright",
branch: "feat/login",
commit: "abc123",
baselineRef: "main",
});
FieldTypeDefaultDescription
suitestring(required)Suite identifier
branchstring(required)Git branch
commitstring(required)Git commit SHA
baselineRefstringBaseline reference
autoApprovebooleanAuto-approve all uploads
isLocalbooleanDevelopment capture

Returns: { sessionId, captureId, diffId }


Complete a session, triggering carry-forward and GitHub check run posting. A session may contain diffs from multiple suites.

const result = await client.completeSession("session_abc", {
exitCode: 0,
});
console.log(result.imageToken); // signed token for image access
for (const diff of result.diffs) {
console.log(diff.suite); // "my-suite"
console.log(diff.diffId); // "diff_xyz"
console.log(diff.conclusion); // "success" | "failure" | null
console.log(diff.reviewUrl); // URL to review UI
console.log(diff.summary); // { changed, new, unchanged, removed, failed }
console.log(diff.changedSnapshots); // ["button", "header"]
}

Returns: { imageToken, diffs } where each diff contains { diffId, suite, conclusion, checkRunUrl, reviewUrl, summary, changedSnapshots }


All methods throw PxdiffError on failure.

import { PxdiffClient, PxdiffError } from "@pxdiff/sdk";
try {
await client.getCapture("invalid-id");
} catch (e) {
if (e instanceof PxdiffError) {
console.error(e.code); // "CAPTURE_NOT_FOUND"
console.error(e.statusCode); // 404
console.error(e.message); // "Capture not found"
}
}
PropertyTypeDescription
codestringError code (e.g. "VALIDATION_ERROR", "RATE_LIMITED")
messagestringHuman-readable message
statusCodenumberHTTP status code
retryAfternumber | undefinedSeconds to wait (from Retry-After header, present on 429 responses)

Retryable errors (429, 500, 502, 503) are automatically retried with exponential backoff before being thrown. See Rate Limits for details.


Exported from @pxdiff/sdk/git.

Detect the current git branch. Checks CI environment variables first, then falls back to git.

import { detectBranch } from "@pxdiff/sdk/git";
const branch = await detectBranch();

Detection order: GITHUB_HEAD_REFGITHUB_REFCI_COMMIT_REF_NAMEgit rev-parse --abbrev-ref HEAD


Detect the current git commit SHA.

import { detectCommit } from "@pxdiff/sdk/git";
const commit = await detectCommit();

Detection order: GitHub PR event payload → GITHUB_SHACI_COMMIT_SHAgit rev-parse HEAD


Detect the baseline branch reference (synchronous).

import { detectBaselineRef } from "@pxdiff/sdk/git";
const ref = detectBaselineRef(); // "main" or null

Detection order: GITHUB_BASE_REFPXDIFF_BASELINE_REFnull


Find the merge-base commit between HEAD and a remote branch.

import { getGitMergeBase } from "@pxdiff/sdk/git";
const mergeBase = await getGitMergeBase("main"); // commit SHA or null

Convert Storybook story entries into concrete capture targets. Handles mode explosion, viewport explosion, and parameter passthrough.

import { explodeStoryTargets } from "@pxdiff/sdk";
const stories = await client.discoverStories(siteId);
const targets = explodeStoryTargets(stories);
const capture = await client.createCapture({
suite: "storybook",
branch: "main",
commit: "abc123",
targets,
});

Behavior:

  • Stories with parameters.disable: true are filtered out
  • Each mode in parameters.modes produces a separate target with [modeName] suffix
  • If no modes, each entry in parameters.viewports produces a target with __width suffix
  • Copies delay, selector, cropToViewport, ignoreSelectors to all generated targets
  • Includes framework: "storybook", storyId, and tags in metadata

interface Capture {
id: string;
suite: string;
branch: string | null;
commit: string | null;
status: "pending" | "capturing" | "processing" | "completed" | "failed";
snapshots: Snapshot[];
totalSnapshots: number;
completedSnapshots: number;
failedSnapshots: number;
partial: boolean;
ephemeral: boolean;
autoApprove: boolean;
createdAt: string;
}
interface Snapshot {
name: string;
viewport: { width: number; height: number } | null;
imageUrl: string;
}
interface Diff {
id: string;
suite: string;
status: "processing" | "completed" | "failed";
headCaptureId: string;
baseCaptureId: string | null;
baselineRef: string | null;
isStale: boolean;
targetRefs: string[] | null;
isLocal: boolean;
results: DiffResult[];
reviewUrl: string | null;
createdAt: string;
}
interface DiffResult {
name: string;
viewport: { width: number; height: number } | null;
status: "unchanged" | "changed" | "new" | "removed" | "skipped";
diffPercentage: number | null;
diffImageUrl: string | null;
currentImageUrl: string;
baselineImageUrl: string | null;
headHash: string | null;
approvalStatus: "pending" | "approved" | "rejected";
baselineSource: string | null;
}
interface SnapshotDiffResult {
name: string;
suite: string;
status: "new" | "changed" | "unchanged";
diffPercentage: number | null;
diffId: string;
captureId: string;
currentS3Key: string | null;
baselineS3Key: string | null;
diffS3Key: string | null;
path: string[] | null;
approvalStatus: "pending" | "approved" | "rejected";
baselineSource: string | null;
}
interface CaptureTarget {
name: string;
url: string;
selector?: string;
viewport?: { width: number; height: number };
framework?: "storybook";
delay?: number;
cropToViewport?: boolean;
ignoreSelectors?: string[];
path?: string[];
metadata?: Record<string, unknown>;
}
interface Site {
id: string;
project: string;
status: "pending" | "uploading" | "ready" | "failed";
fileCount: number;
totalBytes: number;
cdnUrl: string;
createdAt: string;
error: string | null;
}
interface StoryEntry {
url: string;
name: string;
id?: string;
title?: string;
tags?: string[];
parameters?: {
delay?: number;
modes?: Record<string, Record<string, unknown>>;
viewports?: number[];
disable?: boolean;
selector?: string;
cropToViewport?: boolean;
ignoreSelectors?: string[];
diffThreshold?: number;
pauseAnimationAtEnd?: boolean;
forcedColors?: string;
prefersReducedMotion?: string;
media?: string;
};
}