Scripts
Scripts give you a full programming language — Groovy — to orchestrate probes, loop, branch, format output, and implement any logic that declarative chains can’t express.
If chains are a visual flowchart, scripts are code.

When to use scripts vs. chains
Section titled “When to use scripts vs. chains”| Chains | Scripts | |
|---|---|---|
| Runs probes | ✓ | ✓ |
| Variable extraction | ✓ | ✓ |
| Conditional logic | ✓ (CONDITION step) | ✓ |
| Arithmetic / string formatting | limited (Groovy in CONDITION) | ✓ |
| Iteration over dynamic lists | limited (ITERATE step) | ✓ |
| Full output control | ✗ | ✓ |
Use chains when your workflow is a linear sequence with no branching. Use scripts for everything else.
Creating a script
Section titled “Creating a script”
The Scripts view has a permanent sidebar on the left listing all scripts for the active project. Click any script to open it in the editor on the right. Switching the active project updates the list instantly.
Click the + button at the top of the sidebar to create a new script — the script name is required before it can be saved. The script is automatically assigned to the active project.
You can also create scripts from the project dashboard: open the project picker in the app bar, click Project Dashboard, and click New Script in the Scripts section.
The editor
Section titled “The editor”
The editor is a full code editor with Groovy syntax highlighting, line numbers, bracket matching and code folding. It follows the app’s light/dark theme. Click Run — VirtuProbe saves the script automatically before executing it.
Autocomplete
Section titled “Autocomplete”Press Ctrl+Space (or just keep typing) to get context-aware suggestions:
- after
vp.— thevp.send(...)API - inside
vp.send("…")— your bundle names, then the probe names in the chosen bundle (respecting the active project’s visibility) - inside
.extract("…")— the available extractor IDs, grouped by protocol - after
{{— the active environment’s variable names - on a result variable (e.g.
result.) —VpProbeResultmembers (extract,isSuccess, and response fields likestatusCode/headers/body)

Live error checking
Section titled “Live error checking”The editor compile-checks your Groovy as you type and underlines syntax errors in place, with the compiler message on hover — so you catch mistakes before you run. The check is compile-only; it never executes your script.

Keyboard shortcuts
Section titled “Keyboard shortcuts”- Ctrl+S — save
- Ctrl+Enter — save and run
The vp API
Section titled “The vp API”Every script has a vp object injected into scope. It is the entry point for sending probes.
vp.send(bundleName, probeName)
Section titled “vp.send(bundleName, probeName)”Sends a probe using its saved configuration and returns a VpProbeResult. The first argument is the bundle name — the container the probe lives in.
def result = vp.send("My Bundle", "Health check")println result.isSuccess()vp.send(bundleName, probeName, variables)
Section titled “vp.send(bundleName, probeName, variables)”Sends a probe with variable overrides. Variables are merged on top of any active environment variables. Pass a Groovy map literal.
def result = vp.send("My Bundle", "Actuator metric", [stage: "dns.check"])The variable map uses the same {{variableName}} substitution as chains and environments — any field in the probe that contains {{stage}} will have it replaced with "dns.check" at runtime.
VpProbeResult
Section titled “VpProbeResult”vp.send() returns a VpProbeResult. It exposes two methods:
.extract(extractorId, expression)
Section titled “.extract(extractorId, expression)”Extracts a value from the response using any of the built-in extractor types.
def status = result.extract("HTTP_STATUS", null)def token = result.extract("HTTP_JSON_PATH", "$.data.token")def body = result.extract("HTTP_BODY_RAW", null)Pass null as the expression for extractors that don’t use it (HTTP_STATUS, HTTP_BODY_RAW, SMTP_SUCCESS, etc.).
.isSuccess()
Section titled “.isSuccess()”Returns true if the probe executed without assertion failures. Delegates to the protocol’s _SUCCESS extractor.
if (!result.isSuccess()) { println "Probe failed!"}Output
Section titled “Output”println writes to the Output panel below the editor. The full output of the script run is shown there after execution completes.

println "Starting scan..."println "Done. ${count} items processed."Full example — benchmarking API stages
Section titled “Full example — benchmarking API stages”This replicates the VD-48 use case: iterate over a list of stage names, query an HTTP actuator endpoint for each, and print a formatted report.
Assume you have an HTTP probe named "Actuator metric" in bundle "Backend" with the URL http://localhost:8080/actuator/metrics/domain.check.stage?tag=stage:{{stage}}.
def stages = [ "processing", "dns.checkDomainHasARecord", "dns.checkDNS", "html.checkHTML", "evaluation.evaluateFinalDomainStatusForMemos"]
stages.each { stage -> def r = vp.send("Backend", "Actuator metric", [stage: stage])
def count = r.extract("HTTP_JSON_PATH", '$.measurements[?(@.statistic=="COUNT")].value[0]') def total = r.extract("HTTP_JSON_PATH", '$.measurements[?(@.statistic=="TOTAL_TIME")].value[0]') def max = r.extract("HTTP_JSON_PATH", '$.measurements[?(@.statistic=="MAX")].value[0]')
if (count && count != "0") { def avg = (total.toDouble() / count.toDouble() * 1000).toLong() def maxMs = (max.toDouble() * 1000).toLong() println "${stage}: count=${count} avg=${avg}ms max=${maxMs}ms" } else { println "${stage}: no data" }}Example output:
processing: count=142 avg=23ms max=890msdns.checkDomainHasARecord: count=142 avg=48ms max=310msdns.checkDNS: no datahtml.checkHTML: count=89 avg=195ms max=1240msevaluation.evaluateFinalDomainStatusForMemos: count=142 avg=7ms max=44msError handling
Section titled “Error handling”If the script throws an exception — including a syntax error — the Error panel shows the exception message. Output produced before the error is still visible.

// If "My Bundle" does not exist, vp.send() throws:// IllegalArgumentException: Bundle not found: My Bundledef r = vp.send("My Bundle", "Health check")Available Groovy features
Section titled “Available Groovy features”Scripts run inside a standard GroovyShell. The full Groovy language is available — closures, GStrings, collection methods, regular expressions, and more. Third-party library imports (@Grab) are not available.
// GStringsprintln "Status: ${result.extract('HTTP_STATUS', null)}"
// Closures and collection methodsdef statuses = probeNames.collect { name -> vp.send("My Bundle", name).isSuccess()}
// Multi-line stringsdef body = """{ "action": "verify", "token": "${token}"}"""Extractors reference
Section titled “Extractors reference”All extractor types available in chains are also available in scripts via .extract(). See the full Extractors reference.
| Extractor ID | Protocol | Expression |
|---|---|---|
HTTP_STATUS | HTTP | (none) |
HTTP_JSON_PATH | HTTP | JSONPath string |
HTTP_BODY_RAW | HTTP | (none) |
HTTP_HEADER | HTTP | Header name (case-insensitive) |
SMTP_SUCCESS | SMTP | (none) |
SMTP_EXCHANGE_LINES | SMTP | Command string |
IMAP_SUCCESS | IMAP | (none) |
IMAP_EXCHANGE_LINES | IMAP | Command string |
LDAP_SUCCESS | LDAP | (none) |
LDAP_EXCHANGE_RESULT_CODE | LDAP | Operation prefix |
DNS_SUCCESS | DNS | (none) |
DNS_ANSWER_VALUE | DNS | Question prefix (e.g. A example.com) |
REGEX | any | Regex with one capture group |
CONSTANT | any | Literal string value |