Page loaders (URL-triggered automation)
Page loaders are like Greasemonkey/Tampermonkey userscripts but for the HTTP API. You define a set of actions that automatically run whenever the browser navigates to a matching URL. Instead of manually sending a sequence of commands every time you visit a site, you write it once as a YAML file and the container handles it.
Use them to remove cookie popups, dismiss overlays, wait for dynamic content, clean up pages before scraping, or handle any repetitive setup you’d otherwise do manually every time.
How they work
- You create YAML files that define URL patterns and a list of steps.
- Mount those files into the container at
/loaders. - Whenever
gotonavigates to a URL that matches a loader’s pattern, the loader’s steps run automatically instead of the default navigation.
The steps are the exact same actions as the HTTP API. Every action you can send via POST / (goto, eval, click, system_click, sleep, scroll, wait_for_element, etc.) works as a loader step. Same names, same parameters.
Setup
docker run -d -p 8080:8080 -p 5900:5900 \
-v ./my-loaders:/loaders \
psyb0t/stealthy-auto-browse
Set LOADERS_DIR to use a different path inside the container.
Loader YAML format
name: Human-readable name for this loader
match:
domain: example.com # Exact hostname match (www. is stripped automatically)
path_prefix: /articles # URL path must start with this
regex: "article/\\d+" # Full URL must match this regex
steps:
- action: goto # Same actions as the HTTP API
url: "${url}" # ${url} is replaced with the original URL
wait_until: networkidle
- action: eval
expression: "document.querySelector('.cookie-banner')?.remove()"
- action: wait_for_element
selector: "#main-content"
timeout: 10
Match rules
All match fields are optional, but at least one is required. If you specify multiple fields, all of them must match for the loader to trigger:
domain: Exact hostname.www.is stripped from both sides before comparing, sodomain: example.commatcheswww.example.comtoo.path_prefix: The URL path must start with this string.path_prefix: /blogmatches/blog,/blog/post-1,/blog/archive, etc.regex: The full URL is tested against this regular expression.
The ${url} Placeholder
In a top-level string field within a loader step, ${url} is replaced with the original URL passed to goto. This substitution is not recursive into nested objects. Use it in a navigation field; do not interpolate an untrusted URL directly into JavaScript source.
steps:
- action: goto
url: "${url}"
wait_until: networkidle
- action: get_page_info
Practical example: clean scraping
Say you’re scraping a news site that has cookie popups, newsletter modals, and lazy-loaded content. Without a loader, you’d send 5+ commands after every goto. With a loader:
# loaders/news_site.yaml
name: News Site Cleanup
match:
domain: news.example
steps:
# Navigate with full network wait so everything loads
- action: goto
url: "${url}"
wait_until: networkidle
# Wait for the main content to be there
- action: wait_for_element
selector: "article"
timeout: 10
# Kill the cookie popup
- action: eval
expression: "document.querySelector('.cookie-consent')?.remove()"
# Kill the newsletter modal
- action: eval
expression: "document.querySelector('.newsletter-overlay')?.remove()"
# Scroll to trigger lazy-loaded images
- action: scroll_to_bottom
delay: 0.3
# Small pause for everything to settle
- action: sleep
duration: 1
Now when you goto any URL on news.example, all of this happens automatically.
Files are reloaded on each matching navigation and checked in filename order. Only the first matching loader runs. A failed step stops that loader’s sequence; inspect last_result.success, because the wrapper response reports that the loader ran even when its final step failed. Mount only YAML you trust: loaders can execute JavaScript and change page state.
Response when a loader triggers
Your response includes "loader" so you know it fired:
{
"success": true,
"data": {
"loader": "News Site Cleanup",
"steps_executed": 6,
"last_result": {
"success": true,
"timestamp": 1234567890.456,
"data": { "slept": 1 }
}
}
}