Configuration
Complete reference for frontguard.config.ts — all options explained.
Configuration
Frontguard is configured via frontguard.config.ts in your project root. Run frontguard init to generate a starter config.
Full Example
export default {
version: 1,
baseUrl: 'http://localhost:3000',
// Auto-discover routes (zero config)
discover: {
startUrl: '/',
maxDepth: 3,
maxRoutes: 100,
exclude: ['/admin/*', '/api/*'],
},
// Or explicit routes (can be used alongside discover)
// routes: ['/', '/pricing', '/checkout'],
viewports: [375, 768, 1440],
browsers: ['chromium'],
threshold: 0.1,
// AI analysis (optional, BYOK)
ai: {
provider: 'openai',
model: 'gpt-4o',
},
// Ignore dynamic content
ignore: [
{ selector: '.dynamic-timestamp', description: 'Timestamps change on every load' },
{ selector: '.user-avatar', description: 'Avatar may vary' },
{ rect: { x: 0, y: 0, width: 100, height: 50 }, description: 'Ad banner' },
],
// Auth for protected pages
auth: {
storageState: './playwright-auth.json',
},
// Performance
smartRender: true,
workers: 4,
pageTimeout: 30000,
maxHeight: 5000,
viewportHeight: 720,
outputDir: './frontguard-report',
// Anti-flake
antiFlakeRenders: 2,
freezeTime: true,
renderRetries: 1,
// SSIM fallback
ssimFallback: true,
ssimThreshold: 0.98,
// Plugins
plugins: [],
};Reference
Core Options
| Option | Type | Default | Description |
|---|---|---|---|
version | number | 1 | Config schema version |
baseUrl | string | — | Required. Base URL of the application under test |
routes | (string | RouteConfig)[] | — | Explicit routes — plain strings or per-route override objects |
discover | DiscoverOptions | — | Auto-discovery configuration |
viewports | number[] | [375, 768, 1440] | Viewport widths in pixels |
browsers | BrowserEngine[] | ['chromium'] | Browser engines: chromium, firefox, webkit |
threshold | number | 0.1 | Max allowed pixel diff as a fraction (0.0–1.0) |
Discovery Options
| Option | Type | Default | Description |
|---|---|---|---|
startUrl | string | '/' | Starting URL for the crawler |
maxDepth | number | 3 | Maximum link depth to follow |
maxRoutes | number | 100 | Maximum routes to discover |
exclude | string[] | [] | URL patterns (globs) to exclude |
Route discovery supports three modes:
- Crawler — Follows links starting from
startUrl - Filesystem — Reads routes from framework file structure (Next.js
app/, SvelteKitroutes/, etc.) - Config — Explicit
routesarray
Per-Route Overrides
Different pages need different sensitivity. A marketing page with a rotating
testimonial carousel needs a looser threshold than a checkout form. Any entry
in routes can be an object instead of a plain string:
routes: [
'/', // global threshold
{ path: '/checkout', threshold: 0.001 }, // strict — 0.1%
{ path: '/blog/*', threshold: 0.05 }, // lenient — 5%
{ path: '/pricing', ignore: ['.testimonial-carousel'] },
{ path: '/gallery', viewport: [1440] }, // only test at desktop
]| Field | Type | Description |
|---|---|---|
path | string | Required. Route path (the only required field) |
threshold | number | Overrides the global threshold for this route (0.0–1.0) |
ignore | IgnoreRule[] | Additional ignore rules applied only to this route |
viewport | number[] | Restrict this route to specific viewport widths |
label | string | Human-readable label shown in reports |
When threshold is omitted, the route inherits the global threshold.
AI Configuration
| Option | Type | Default | Description |
|---|---|---|---|
provider | 'openai' | 'anthropic' | — | AI provider |
model | string | — | Model identifier (e.g. gpt-4o, claude-sonnet-4-20250514) |
AI is BYOK (Bring Your Own Key). Set your API key via environment variables:
FRONTGUARD_OPENAI_KEYfor OpenAIFRONTGUARD_ANTHROPIC_KEYfor Anthropic
Ignore Rules
Mask dynamic content that causes false positives:
ignore: [
// By CSS selector
{ selector: '.dynamic-timestamp' },
// By pixel region
{ rect: { x: 0, y: 0, width: 300, height: 100 } },
// With description (for documentation)
{
selector: '.ad-banner',
description: 'Third-party ads change on every load',
},
]Authentication
For testing pages behind login:
auth: {
storageState: './playwright-auth.json',
}The storageState file is a Playwright storage state JSON containing cookies, localStorage, and session data. Generate it with:
npx playwright codegen --save-storage=playwright-auth.json http://localhost:3000/loginPerformance Options
| Option | Type | Default | Description |
|---|---|---|---|
smartRender | boolean | true | Wait for animations, fonts, lazy images |
workers | number | 4 | Parallel browser workers |
pageTimeout | number | 30000 | Navigation timeout in ms |
maxHeight | number | 5000 | Max screenshot height in px |
viewportHeight | number | 720 | Viewport height in px |
outputDir | string | './frontguard-report' | Report output directory |
Anti-Flake Options
| Option | Type | Default | Description |
|---|---|---|---|
antiFlakeRenders | number | 1 | Renders per page for flake detection (recommended: 2–3) |
freezeTime | boolean | number | false | Freeze Date.now() during render |
renderRetries | number | 0 | Per-page retry count on render failure |
ssimFallback | boolean | true | Use SSIM perceptual diff for borderline results |
ssimThreshold | number | 0.98 | SSIM score above which images are identical |
Anti-flake rendering: Set antiFlakeRenders: 2 to capture each page twice. If both renders produce different results compared to the baseline, the diff is real. If only one does, it's a flake and gets ignored.
Image Upload (PR Thumbnails)
To embed before/after/diff thumbnails directly in GitHub PR comments, the
images need a public URL — GitHub can't render local file paths. Configure an
imageUpload backend and the PR reporter renders an inline <table> of
thumbnails linking to the full-size images. Without it, PR comments fall back to
a text-only summary.
imageUpload: {
provider: 'r2', // 'r2' | 's3' | 'github-artifacts' | 'local'
bucket: 'frontguard-screenshots',
endpoint: 'https://<account>.r2.cloudflarestorage.com',
publicUrlPrefix: 'https://cdn.example.com', // public domain for the bucket
}| Provider | Use case | Credentials |
|---|---|---|
r2 | Cloudflare R2 (recommended) | FRONTGUARD_S3_ACCESS_KEY / FRONTGUARD_S3_SECRET_KEY |
s3 | AWS S3 | FRONTGUARD_S3_ACCESS_KEY / FRONTGUARD_S3_SECRET_KEY |
github-artifacts | Link to CI run artifacts | Uses GITHUB_* env in Actions |
local | Local dev / CI artifact dir | None |
| Option | Type | Description |
|---|---|---|
provider | string | Required. Backend: r2, s3, github-artifacts, local |
bucket | string | Bucket name (R2/S3) |
region | string | Region (S3; R2 uses auto) |
endpoint | string | Custom endpoint (R2 account endpoint) |
publicUrlPrefix | string | Public URL prefix for a custom domain / public bucket |
project | string | Namespace used in object keys (default frontguard) |
Only "interesting" diffs (regressions, changes, new pages, flakes) are uploaded — not every passing screenshot — keeping storage and PR comments lean.
Baselines
Baselines are stored in a Git orphan branch (frontguard-baselines) by default. This keeps baseline images out of your main branch history while still being version-controlled.
The baseline manifest tracks:
- Which routes have baselines
- Which viewports and browsers were captured
- When each baseline was last updated
{
"schemaVersion": 1,
"createdBy": "frontguard",
"updatedAt": "2025-01-15T10:30:00Z",
"routes": {
"/": {
"viewports": [375, 768, 1440],
"browsers": ["chromium"],
"lastUpdated": "2025-01-15T10:30:00Z"
}
}
}