Skip to content

Workflow Execution

Workflows in Bflow are declarative, version-controllable JSON files. You can execute them headless in CI/CD pipelines or headed on your local desktop.

Headless mode runs Chrome invisibly in the background. It is blazing fast and ideal for automated testing, cron jobs, and CI/CD pipelines:

Terminal window
bun flow workflows/hn-top-stories.json

Headed mode launches a visible Chrome window so you can watch each step execute live in real time:

Terminal window
bun flow workflows/hn-top-stories.json --headed

You can pass dynamic variables into any workflow via CLI arguments. This allows you to write reusable templates:

Terminal window
bun flow workflows/search-workflow.json --query="Bun runtime" --limit=10

Inside your workflow JSON, use {{variableName}} syntax:

{
"name": "Search Workflow",
"startUrl": "https://duckduckgo.com",
"variables": {
"query": "Default Query",
"limit": 5
},
"steps": [
{ "action": "goto", "url": "https://duckduckgo.com" },
{ "action": "type", "selector": "input[name='q']", "text": "{{query}}" },
{ "action": "click", "selector": "button[type='submit']" }
]
}

Nested paths and transformation pipelines are also supported:

{
"action": "type",
"selector": "#email",
"text": "{{row.contact.email | trim | lowercase}}"
}

Use {{env.SECRET_NAME}} for secrets. Missing environment references fail instead of being typed literally. Variable precedence from highest to lowest is system values, CLI overrides, workflow variables, row values, and step-local variables.

Use the data-aware command when every provider row should receive an isolated browser run:

Terminal window
bun workflow run workflows/signup.json \
--data='google-sheets://SPREADSHEET_ID/Users?range=A:E' \
--dry-run

After reviewing the dry-run summary, remove --dry-run. Data runs can filter rows, use bounded parallel workers, retry transient failures, resume checkpoints, and write status/results back without changing source cells. See External Data for the full workflow structure.


Here is a complete workflow example (workflows/hn-top-stories.json):

{
"name": "Hacker News Top Stories",
"startUrl": "https://news.ycombinator.com",
"variables": {
"targetSite": "news.ycombinator.com"
},
"steps": [
{
"action": "goto",
"url": "https://news.ycombinator.com",
"waitUntil": "domcontentloaded"
},
{
"action": "assert",
"text": "Hacker News",
"contains": "Hacker News"
},
{
"action": "extractMultiple",
"containerSelector": ".athing",
"as": "topStories",
"limit": 10,
"fields": {
"title": ".titleline > a",
"url": ".titleline > a@href"
}
},
{
"action": "screenshot",
"path": "output/hn-top.png",
"fullPage": true
},
{
"action": "save",
"path": "output/hn-stories.json",
"format": "json"
}
]
}

When a workflow runs, the CLI logs a step-by-step progress report with execution times:

🌊 Starting flow: Hacker News Top Stories (5 steps)
[1/5] 🌐 goto https://news.ycombinator.com ... βœ“ (284ms)
[2/5] πŸ”Ž assert "Hacker News" ... βœ“ (12ms)
[3/5] πŸ“Š extractMultiple (.athing) -> topStories (10 items) ... βœ“ (45ms)
[4/5] πŸ“· screenshot -> output/hn-top.png ... βœ“ (120ms)
[5/5] πŸ’Ύ save -> output/hn-stories.json ... βœ“ (4ms)
✨ Flow completed successfully in 465ms!

Normal workflow results, screenshots, PDFs, and saved extracts are written beneath output/ relative to the current working directory. Data-driven runs suppress per-row result files and write one workflow-<run-id>-summary.json plus a resumable workflow state file.