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

> Master Maestro YAML variables to inject dynamic values and environment variables into your test flows. Learn the ${...} syntax and unlock powerful automation.

- Repository: [Maestro/Maestro](https://github.com/mobile-dev-inc/Maestro)
- Tags: how-to-guide
- Published: 2026-03-20

---

**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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.

```yaml

# 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt)) that injects variables at a specific point in the flow.

```yaml

# 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`](https://github.com/mobile-dev-inc/Maestro/blob/main/Env.kt).

```yaml

# 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:

```yaml
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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`.

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

```

The JSON is parsed by [`RunFlowTool.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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:

```yaml
- inputText: "\${NOT_A_VARIABLE}"

```

The regex engine in [`Env.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/Env.kt) and validated by test suites like [`RhinoJsEngineTest.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.