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.

FieldDefaultDescription
$schema-JSON Schema URL for IDE autocompletion
outDirvideos/Default output directory for recorded videos (relative to config file)
baseUrl""Prepended to relative video URLs
viewport1080x1080Default 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 pause step instead.

clickDwellrandom 80-180ms

Milliseconds the cursor pauses after reaching its target before clicking. Set to 0 to click instantly.

sfx-Sound effects configuration for click and keystroke sounds
videosrequiredObject 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.

FieldDefaultDescription
urlrequiredURL to navigate to
baseUrlinheritedPrepended to relative URLs
viewportinheritedBrowser viewport dimensions
zoom-CSS zoom level applied to the page
fps60Recording frame rate (1-120)
quality80Output quality (1-100). Higher values produce larger files.
waitFor-CSS selector to wait for before starting steps
output<name>.mp4Output file path (.mp4, .gif, or .webm), resolved relative to outDir
thumbnail

{ time: 0 }

Object with time (seconds) and/or enabled: false to skip

includeinheritedArray of paths to JSON files whose steps are prepended
themeinheritedCursor and HUD overlay customization
defaultDelayinheritedDefault delay (ms) applied after each step
clickDwellinheritedMilliseconds the cursor pauses before clicking
sfxinheritedSound effects configuration for click and keystroke sounds
stepsrequiredArray 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

FieldDefaultDescription
imagebuilt-in arrowPath to a custom cursor SVG file
size24Cursor size in pixels
hotspot"top-left"

Where the click point lands: "top-left" for arrow cursors, "center" for circle/dot cursors

HUD options

FieldDefaultDescription
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: "top" or "bottom"

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": []
    }
  }
}
FieldDefaultDescription
click1Click sound variant (1-4) or path to a custom audio file
key1Keystroke 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:

PresetWidthHeight
desktop19201080
desktop-hd25601440
laptop1366768
macbook-air1440900
macbook-pro1512982
ipad10241366
ipad-pro8341194
ipad-mini7681024
iphone-15393852
iphone-15-pro-max430932
iphone-se375667
pixel-8412915
galaxy-s24360780

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 record

TypeScript 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).

CategoryKeys
Letters

a - z

Digits

0 - 9

Modifiers

mod, cmd, ctrl, shift, alt, meta

Navigation

ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown

Editing

Enter, Tab, Escape, Backspace, Delete, Space

Function

F1 - F12

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-run

JSON 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"
}