We'll build a project from scratch and record a first .mp4. The example records
a tiny git tutorial (any tool works). At the end you'll have
my-tutorial/recordings/01-commit.mp4.
New to the vocabulary (Stage, Scene, Prop)? See Concepts.
A project is just a folder with backstage.json, scenes/, and (optionally)
hooks/.
mkdir -p my-tutorial/scenes my-tutorial/hooks
cd my-tutorialThe config says where to record, what the popup looks like, and which layouts scenes can stage. For a CLI tutorial, one fullscreen terminal is enough.
{
"record": { "monitor": "eDP-1", "fps": 30, "out": "recordings" },
"popup": { "size": [1200, 520], "cps": 32 },
"hooks": { "setup": "hooks/setup.sh", "reset": "hooks/reset.sh" },
"layouts": {
"solo": { "fullscreen": true, "panes": [
{ "name": "term", "cwd": "work", "cmd": "bash" }
] }
}
}outis relative to the project →my-tutorial/recordings/.workis where the terminal opens (the stage for git). The hooks create it.- Set
monitorto yours (hyprctl monitors).
Full reference: Configuration.
Hooks are yours; Backstage just calls them. reset runs before every take;
setup runs when a scene is "fresh". Here both leave a clean git repo in
work/.
hooks/reset.sh:
#!/usr/bin/env bash
set -euo pipefail
PROJECT="$(cd "$(dirname "$0")/.." && pwd)"
WORK="$PROJECT/work"
rm -rf "$WORK"; mkdir -p "$WORK"; cd "$WORK"
git init -q
printf 'hello\n' > hello.txthooks/setup.sh (same as reset here):
#!/usr/bin/env bash
exec "$(dirname "$0")/reset.sh"chmod +x hooks/*.shA scene mixes dialog boxes (typed Prompter) with pane actions. run sends a
command + Enter; target is a pane name.
scenes/01-commit.json:
{
"name": "01-commit",
"layout": "solo",
"reset": true,
"steps": [
{"action": "dialog", "value": "Let's make the first commit in a git repo."},
{"action": "run", "target": "term", "value": "git status", "delay-after": 2},
{"action": "dialog", "value": "hello.txt is untracked. Let's add it."},
{"action": "run", "target": "term", "value": "git add hello.txt", "delay-after": 1.5},
{"action": "run", "target": "term", "value": "git commit -m 'first commit'", "delay-after": 3},
{"action": "dialog", "value": "Done — first commit made."},
{"action": "run", "target": "term", "value": "git log --oneline", "delay-after": 3}
]
}backstage play scenes/01-commit.jsonBackstage finds backstage.json, runs the reset hook, opens the fullscreen
terminal, records, performs the steps, and stops. Output:
recordings/01-commit.mp4. Close the stage afterwards:
backstage killWant to check the flow first, without recording?
backstage rehearse scenes/01-commit.json- Wording / timing: edit the
values anddelay-afters and re-run. Same baseline + same scene → same video. - Inspect a frame without rewatching:
ffmpeg -i recordings/01-commit.mp4 -ss 8 -frames:v 1 /tmp/f.png
- Box too fast/slow: tweak
popup.cpsinbackstage.json.
Add a layout with two panes and target by name:
"split": { "fullscreen": true, "panes": [
{ "name": "app", "cmd": "lazygit", "cwd": "work" },
{ "name": "shell", "cmd": "bash", "cwd": "work", "size": "38%" }
] }{"action": "keys", "target": "app", "commands": ["down", "space", "c"]}Backstage records the whole screen, so a prop step that launches a browser is
captured. This is how you turn an e2e suite into a feature-documentation video:
point a Prop at the run.
{
"name": "feature-tour",
"layout": "solo",
"steps": [
{"action": "dialog", "value": "Walking through the checkout flow."},
{"action": "prop", "value": "props/e2e.sh", "args": ["checkout.spec.ts"], "delay-after": 1}
]
}props/e2e.sh runs your tool headed (so it's visible to the recorder), e.g.:
#!/usr/bin/env bash
exec npx playwright test "$1" --headedThe tests already click through every feature; the run becomes the demo, regenerated whenever you re-record.
Record several scenes back to back with a titled slide between them. Declare a
transition (any command that writes an mp4 to {{out}}) and a production in
backstage.json:
backstage produce intro # → recordings/production.mp4Each scene is recorded, the to-log slide is rendered between them, and the
clips are concatenated into one video. Staging is hidden by default; add
--show-staging to include it. Ad-hoc, without declaring a production:
backstage produce --scenes 01-commit,02-log --transition to-logNext: Writing scenes (full action + key reference) · Configuration (productions + transitions) · How it works (the pipeline inside).