How to Use Variables in Maestro YAML: A Complete Guide to Environment Variables and Dynamic Values

Maestro YAML variables allow you to inject dynamic values into test flows using ${…} syntax, which evaluates JavaScript expressions at runtime against environment variables defined in flow-level blocks, CLI arguments, or runtime commands.

In the mobile-dev-inc/Maestro repository, variables are a core mechanism for creating reusable, data-driven test flows. The system evaluates every ${…} placeholder as a JavaScript expression using the JsEngine, enabling everything from simple variable substitution to complex logic. This functionality is implemented in maestro-orchestra-models/src/main/java/maestro/orchestra/util/Env.kt within the Env.evaluateScripts method.

Where Maestro YAML Variables Come From

Variables can originate from three distinct sources, each with different scoping rules and injection methods.

Flow-Level env Block

The most common approach is declaring variables at the top of a YAML file using the env key. These variables are visible to every command in that flow, including the appId declaration.


# flow_with_vars.yaml

appId: ${APP_ID}
env:
  APP_ID: com.example.myapp
  USERNAME: test_user
---
- launchApp
- tapOn: "Login"
- inputText: ${USERNAME}

As implemented in Env.kt, these maps are merged into the environment before command execution begins.

DefineVariablesCommand at Runtime

For dynamic values generated during test execution, Maestro provides the defineVariables command. This creates a DefineVariablesCommand (defined in maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt) that injects variables at a specific point in the flow.


# flow_define_mid.yaml

appId: com.example.app
---
- launchApp
- evalScript: ${ const token = fetchAuth(); }
- defineVariables:
    AUTH_TOKEN: ${token}
- tapOn: "Continue"
- inputText: ${AUTH_TOKEN}

Variables defined this way are visible from the point of definition onward, including in nested sub-flows.

Default and Internal Variables

Maestro automatically injects several read-only variables that provide metadata about the execution context. These include MAESTRO_FILENAME, MAESTRO_DEVICE_UDID, and MAESTRO_APP_ID. These are reserved variables that cannot be overridden via CLI or flow-level env blocks, as enforced by the withDefaultEnvVars logic in Env.kt.


# flow_defaults.yaml

appId: ${MAESTRO_APP_ID}
---
- launchApp
- tapOn: "Open ${MAESTRO_FILENAME}"

How Variable Interpolation Works

When Maestro parses a flow, every string field undergoes script evaluation through Env.evaluateScripts. The process follows a specific pipeline:

  1. Pattern Matching: The regex (?<!\\)\${([^$]*)} identifies all ${…} placeholders that are not escaped with a backslash
  2. JavaScript Evaluation: The content inside the brackets is executed as JavaScript using the configured JsEngine (Rhino or Graal)
  3. Substitution: The placeholder is replaced with the evaluation result, coerced to a string

This enables complex expressions beyond simple variable lookup:

appId: ${APP_ID || 'com.example.default'}
inputText: ${'User' + Math.floor(Math.random() * 1000)}

Variable Scoping and Isolation

Understanding scope prevents variable leakage and ensures predictable test execution.

Flow-Level Isolation

Variables defined in a sub-flow using the env block or defineVariables are isolated to that flow. They do not leak back to the parent flow unless explicitly passed via output parameters.

Environment Isolation

The test suite in maestro-test/src/test/kotlin/maestro/test/RhinoJsEngineTest.kt validates that variables from one flow execution do not affect another. Each flow run maintains its own environment map, preventing cross-test contamination.

CLI Override Precedence

When using the -e or --env flag, CLI-provided variables take precedence over flow-level env declarations but cannot override reserved internal variables like MAESTRO_SHARD_ID.

maestro test flow_with_vars.yaml -e '{"USERNAME":"alice","PASSWORD":"s3cr3t"}'

The JSON is parsed by RunFlowTool.kt in maestro-cli/src/main/java/maestro/cli/mcp/tools/ and merged into the environment before execution.

Escaping Variables

To prevent interpolation and treat ${...} as literal text, prefix it with a backslash:

- inputText: "\${NOT_A_VARIABLE}"

The regex engine in Env.kt ignores escaped patterns, leaving the literal string ${NOT_A_VARIABLE} in the output.

Summary

  • Maestro YAML variables use ${…} syntax that evaluates JavaScript expressions at runtime via Env.evaluateScripts in Env.kt
  • Three sources provide variables: flow-level env blocks, runtime defineVariables commands, and reserved internal variables like MAESTRO_FILENAME
  • Scoping keeps sub-flow variables isolated from parent flows, while CLI -e flags override flow-level defaults
  • JavaScript support inside ${…} enables defaults (||), concatenation, and method calls
  • Escaping with \${} prevents interpolation when literal text is needed

Frequently Asked Questions

How do I set default values for Maestro YAML variables?

Use JavaScript's logical OR operator inside the interpolation expression. For example: ${API_URL || 'https://api.example.com'}. If API_URL is undefined, the expression evaluates to the fallback string. This is parsed by the JsEngine during Env.evaluateScripts execution.

Can I override Maestro variables from the command line?

Yes, pass a JSON object using the -e or --env flag: maestro test flow.yaml -e '{"VAR":"value"}'. These CLI variables merge with and override flow-level env declarations, but they cannot override reserved internal variables like MAESTRO_DEVICE_UDID or MAESTRO_SHARD_INDEX.

Why are my variables not available in sub-flows?

Variables defined inside a sub-flow using defineVariables or the env block are scoped to that sub-flow only. To pass data back to the parent flow, you must use explicit output parameters in the runFlow command. This isolation is enforced by the environment merging logic in Env.kt and validated by test suites like RhinoJsEngineTest.kt.

How do I prevent Maestro from interpreting ${...} as a variable?

Escape the dollar sign with a backslash: \${...}. The regex pattern (?<!\\)\${([^$]*)} in Env.evaluateScripts specifically ignores escaped sequences, treating \${TEXT} as literal text rather than a JavaScript expression to evaluate.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →