Configuration
webreel uses a config file in your project root. The default is webreel.config.json (JSONC; comments are supported). TypeScript (webreel.config.ts) and JavaScript (webreel.config.js, .mjs) configs are also supported via defineConfig.
webreel automatically searches parent directories for a config file, so in a monorepo you can place the config at the root and run commands from subdirectories.
Example
{
"$schema": "https://webreel.dev/schema/v1.json",
"outDir": "./videos",
"baseUrl": "https://myapp.com",
"viewport": { "width": 1920, "height": 1080 },
"defaultDelay": 500,
"videos": {
"hero": {
"url": "/",
"steps": [
{ "action": "pause", "ms": 500 },
{ "action": "click", "text": "Get Started", "delay": 1000 }
]
},
"login": {
"url": "/login",
"output": "login-flow.mp4",
"steps": [
{
"action": "type",
"text": "user@example.com",
"target": { "selector": "#email" }
},
{ "action": "click", "text": "Sign In" }
]
}
}
}Top-level options
These fields apply as defaults to all videos and can be overridden per-video.
| Field | Default | Description |
|---|---|---|
$schema | - | JSON Schema URL for IDE autocompletion |
outDir | videos/ | Default output directory for recorded videos (relative to config file) |
baseUrl | "" | Prepended to relative video URLs |
viewport | 1080x1080 | Default browser viewport dimensions |
theme | - | Default cursor and HUD overlay customization |
include | - | Array of step files prepended to all videos |
defaultDelay | - | Default delay (ms) applied after each step. For explicit waits, use a
|
clickDwell | random 80-180ms | Milliseconds the cursor pauses after reaching its target before clicking. Set to
|
sfx | - | Sound effects configuration for click and keystroke sounds |
videos | required | Object mapping video names to their configurations |
Video options
Each key in the videos object is the video name (used as the default output filename). Per-video values override top-level defaults.
| Field | Default | Description |
|---|---|---|
url | required | URL to navigate to |
baseUrl | inherited | Prepended to relative URLs |
viewport | inherited | Browser viewport dimensions |
zoom | - | CSS zoom level applied to the page |
fps | 60 | Recording frame rate (1-120) |
quality | 80 | Output quality (1-100). Higher values produce larger files. |
waitFor | - | CSS selector to wait for before starting steps |
output | <name>.mp4 | Output file path (.mp4, .gif, or .webm), resolved relative to outDir |
thumbnail | | Object with |
include | inherited | Array of paths to JSON files whose steps are prepended |
theme | inherited | Cursor and HUD overlay customization |
defaultDelay | inherited | Default delay (ms) applied after each step |
clickDwell | inherited | Milliseconds the cursor pauses before clicking |
sfx | inherited | Sound effects configuration for click and keystroke sounds |
steps | required | Array of action steps to execute |
delay and defaultDelay
Every step (except pause) accepts an optional delay field: the number of milliseconds to wait after the step executes. Set defaultDelay at the top level or per-video to apply a default delay to all steps. A step's own delay overrides defaultDelay. For explicit waits that are not tied to a specific step, use a pause step instead.
{
"defaultDelay": 500,
"videos": {
"hero": {
"url": "/",
"steps": [
{ "action": "click", "text": "Get Started" },
{ "action": "key", "key": "mod+a", "delay": 1000 },
{ "action": "click", "text": "Submit" }
]
}
}
}clickDwell
When the cursor reaches its target, it pauses briefly before clicking, just like a real human hovering to confirm they're in the right spot. By default, this dwell is a random 80-180ms. Set clickDwell to a fixed number of milliseconds, or 0 to click instantly.
{
"clickDwell": 120,
"videos": {
"hero": {
"url": "/",
"clickDwell": 0,
"steps": [{ "action": "click", "text": "Get Started" }]
}
}
}Step label and description
Every step accepts an optional label field for HUD display and an optional description field for documentation. Labels are shown on-screen during recording (like keystroke labels). Descriptions are shown in --verbose output and --dry-run summaries.
{
"action": "click",
"text": "Get Started",
"label": "CTA",
"description": "Click the CTA button on the hero section"
}outDir
Controls where output videos are written. Resolved relative to the config file location. Defaults to videos/ when not specified. When a video does not specify output, it defaults to <name>.mp4 inside outDir.
{
"outDir": "./dist",
"videos": {
"hero": { "url": "https://example.com", "steps": [] }
}
}This writes ./dist/hero.mp4. Without outDir, it would write ./videos/hero.mp4.
baseUrl
When set at the top level, all videos inherit it. Videos can override it.
{
"baseUrl": "https://myapp.com",
"videos": {
"home": { "url": "/", "steps": [] },
"docs": { "url": "/docs", "steps": [] }
}
}viewport
Controls the browser window dimensions. Defaults to 1080x1080. Set at the top level to apply to all videos.
{
"viewport": { "width": 1920, "height": 1080 }
}zoom
CSS zoom level applied to the page. Makes content appear larger within the viewport. For example, "zoom": 2 doubles the size of all elements.
waitFor
Element to wait for before starting the video steps. Accepts a CSS selector string or an object with text or selector (and optional within for scoping).
{
"videos": {
"app": {
"url": "https://example.com",
"waitFor": "[data-ready]",
"steps": []
},
"dashboard": {
"url": "https://example.com/app",
"waitFor": { "text": "Welcome back" },
"steps": []
}
}
}output
Override the default output path per video. Resolved relative to outDir. Supported formats: .mp4, .gif, .webm.
{
"videos": {
"hero": {
"url": "https://example.com",
"output": "hero-video.mp4",
"steps": []
}
}
}thumbnail
A PNG thumbnail is generated for every recorded video by default, using the first frame (time 0). Use the thumbnail object to customize the time or disable generation.
{
"videos": {
"hero": {
"url": "https://example.com",
"thumbnail": { "time": 2.5 },
"steps": []
},
"background": {
"url": "https://example.com",
"thumbnail": { "enabled": false },
"steps": []
}
}
}The thumbnail is written alongside the video as <name>.png.
include
Prepend steps from other files. Can be set at the top level (applies to all videos) or per-video. Supports .json, .ts, .js, and .mjs files.
{
"include": ["./shared/login-steps.json"],
"videos": {
"dashboard": {
"url": "/dashboard",
"steps": [{ "action": "click", "text": "Analytics" }]
}
}
}JSON include files should contain a steps array:
{
"steps": [
{ "action": "click", "text": "Sign In" },
{ "action": "type", "text": "user@example.com", "target": { "selector": "#email" } }
]
}TypeScript/JavaScript include files should export an object with a steps array:
export default {
steps: [
{ action: "click", text: "Sign In" },
{ action: "type", text: "user@example.com", target: { selector: "#email" } },
],
};TypeScript includes require using webreel record (async loading). The sync loadWebreelConfig function only supports JSON includes.
theme
Customize the cursor and keystroke overlay appearance. Can be set at the top level or per-video.
{
"theme": {
"cursor": {
"image": "path/to/cursor.svg",
"size": 32,
"hotspot": "top-left"
},
"hud": {
"background": "rgba(0,0,0,0.7)",
"color": "white",
"fontSize": 48,
"fontFamily": "monospace",
"borderRadius": 12,
"position": "bottom"
}
}
}Cursor options
| Field | Default | Description |
|---|---|---|
image | built-in arrow | Path to a custom cursor SVG file |
size | 24 | Cursor size in pixels |
hotspot | "top-left" | Where the click point lands: |
HUD options
| Field | Default | Description |
|---|---|---|
background | - | CSS background color for the keystroke HUD |
color | - | CSS text color for the keystroke HUD |
fontSize | - | Font size in pixels for the keystroke HUD |
fontFamily | - | CSS font-family for the keystroke HUD |
borderRadius | - | Border radius in pixels for the keystroke HUD |
position | "bottom" | Where the HUD appears: |
fps
Recording frame rate. Defaults to 60. Lower values (e.g. 30) produce smaller files and are useful for GIF output.
{
"videos": {
"hero": {
"url": "https://example.com",
"fps": 30,
"steps": []
}
}
}quality
Output quality on a scale of 1-100, where 100 is the highest quality and largest file size. Defaults to 80. Maps to CRF values internally for MP4 and WebM.
{
"videos": {
"hero": {
"url": "https://example.com",
"quality": 95,
"steps": []
}
}
}sfx
Add click and keystroke sound effects to recorded videos. Set at the top level or per-video. Each key (click and key) accepts a built-in variant (1, 2, 3, or 4) or a path to a custom audio file.
{
"sfx": { "click": 1, "key": 2 },
"videos": {
"hero": {
"url": "/",
"steps": [{ "action": "click", "text": "Get Started" }]
},
"silent": {
"url": "/about",
"sfx": { "click": 3, "key": "./sounds/custom-key.mp3" },
"steps": []
}
}
}| Field | Default | Description |
|---|---|---|
click | 1 | Click sound variant (1-4) or path to a custom audio file |
key | 1 | Keystroke sound variant (1-4) or path to a custom audio file |
When sfx is set, sound events are mixed into the final video. Custom file paths are resolved relative to the config file location. When sfx is not set, videos are silent.
Viewport presets
In addition to explicit { "width": ..., "height": ... } objects, the viewport field accepts named device presets:
{
"viewport": "iphone-15",
"videos": {
"mobile": { "url": "/", "steps": [] }
}
}Available presets:
| Preset | Width | Height |
|---|---|---|
desktop | 1920 | 1080 |
desktop-hd | 2560 | 1440 |
laptop | 1366 | 768 |
macbook-air | 1440 | 900 |
macbook-pro | 1512 | 982 |
ipad | 1024 | 1366 |
ipad-pro | 834 | 1194 |
ipad-mini | 768 | 1024 |
iphone-15 | 393 | 852 |
iphone-15-pro-max | 430 | 932 |
iphone-se | 375 | 667 |
pixel-8 | 412 | 915 |
galaxy-s24 | 360 | 780 |
Environment variables
String values in JSON configs support environment variable substitution using $VAR or ${VAR} syntax. Variables that are not set in the environment are left as-is.
{
"baseUrl": "$BASE_URL",
"videos": {
"hero": {
"url": "${APP_PATH}/dashboard",
"steps": []
}
}
}BASE_URL=https://staging.myapp.com webreel recordTypeScript configs can use process.env directly, so this feature is most useful for JSON configs.
Key reference
The key field on key steps accepts single keys or combos joined with +. Use mod for the platform modifier (Cmd on macOS, Ctrl elsewhere).
| Category | Keys |
|---|---|
| Letters |
|
| Digits |
|
| Modifiers |
|
| Navigation |
|
| Editing |
|
| Function |
|
Examples:
{ "action": "key", "key": "mod+a" }
{ "action": "key", "key": "cmd+shift+p" }
{ "action": "key", "key": "Enter" }
{ "action": "key", "key": "Escape" }defineConfig
For TypeScript users, create a webreel.config.ts file and use the defineConfig helper for type-safe config authoring:
import { defineConfig } from "webreel";
export default defineConfig({
videos: {
hero: {
url: "/",
steps: [{ action: "click", text: "Get Started" }],
},
},
});The InputWebreelConfig and InputVideoConfig types are also exported for use in your own code:
import type { InputWebreelConfig } from "webreel";JavaScript configs (webreel.config.js, webreel.config.mjs) work the same way: just export default a config object.
Dry run
Use webreel record --dry-run to print the fully resolved config (with defaults applied, includes merged) and step list without launching a browser. This is useful for debugging complex configs.
webreel record --dry-run
webreel record hero --dry-runJSON Schema
Add the $schema field to get autocompletion and validation in IDEs that support JSON Schema (VS Code, Cursor, JetBrains, etc.):
{
"$schema": "https://webreel.dev/schema/v1.json"
}