# How to Manage Environments in Maestro YAML: CLI Injection, Dynamic Variables, and Scope Isolation

> Master Maestro YAML environments with CLI injection, dynamic variables, and scope isolation. Learn how to effectively manage your testing workflows for better control and flexibility. Discover the Orchestra.kt implementation.

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

---

**Maestro injects environment variables into YAML flows using the `${VAR}` syntax, supporting CLI flags, dynamic definitions, and isolated sub-flow scopes as implemented in [`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt) and [`RunFlowTool.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/RunFlowTool.kt).**

The mobile-dev-inc/Maestro repository provides a robust environment management system that allows you to parameterize test flows without hardcoding values. By leveraging the `${NAME}` substitution syntax and the engine's scoping mechanisms, you can manage secrets, configuration flags, and runtime data across single flows and complex multi-flow hierarchies.

## Injecting Environment Variables from the CLI

The most common method to manage environments in Maestro YAML is passing a JSON map via the command line when executing `maestro run`. The CLI parses this map in **[`maestro-cli/src/main/java/maestro/cli/mcp/tools/RunFlowTool.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/mcp/tools/RunFlowTool.kt)** (lines 69-106) and injects the values into the flow's execution context before any commands execute.

```bash

# Run a flow and provide two variables

maestro run -e '{"USERNAME":"test_user","PASSWORD":"s3cr3t"}' my_flow.yml

```

Inside your YAML file, reference these values using the `${VARIABLE_NAME}` syntax:

```yaml

# my_flow.yml

- launchApp: com.example.app
- tapOn: "login_button"
- inputText: "${USERNAME}"
- inputText: "${PASSWORD}"
- tapOn: "submit_button"

```

The substitution occurs **after** the YAML file is parsed but **before** the command is sent to the device, ensuring sensitive data never appears in your version-controlled test files.

## Dynamic Variable Definition with DefineVariables

For values generated at runtime—such as timestamps, random IDs, or API tokens—you can create or overwrite variables dynamically using the **`DefineVariables`** command. This command evaluates a JavaScript/Kotlin snippet and stores the result in the environment map via `jsEngine.putEnv`, as implemented in **[`maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt)** at line 536.

```yaml

# flow.yml

- launchApp: com.example.app
- defineVariables:
    outputVariable: "authToken"
    script: |
      // Kotlin/JS snippet; returns a token string
      return "token_" + System.currentTimeMillis()
- tapOn: "settings"
- assertVisible: "Token: ${authToken}"

```

The **`DefineVariablesCommand`** data class (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)**) drives this behavior, allowing any subsequent command in the flow to reference the newly created `${authToken}` variable.

## Variable Scoping and Isolation

Maestro guarantees **environment isolation** between flows and sub-flows through the `EnvironmentScope` mechanism. When entering a sub-flow, **[`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt)** (line 1037) creates a new scope that isolates variables per execution branch, preventing leaks between parallel flows or nested hierarchies.

### Overriding Variables in Sub-flows

You can override environment variables for a specific sub-flow without affecting the parent context or other parallel executions using the `env` block on a `runFlow` command:

```bash

# Parent flow runs with global environment

maestro run -e '{"ENV":"prod"}' parent.yml

```

```yaml

# parent.yml

- runFlow:
    file: subflow.yml
    env:
      ENV: "staging"   # overrides the CLI value just for this sub-flow

```

```yaml

# subflow.yml

- assertVisible: "Running in ${ENV} mode"

```

This per-flow scoping is backed by GraalJS isolation behavior documented in the repository's **[`CHANGELOG.md`](https://github.com/mobile-dev-inc/Maestro/blob/main/CHANGELOG.md)** (line 109), ensuring that a variable set in one flow never leaks into another, even during parallel test execution.

## Default Environment Variables

Maestro automatically injects several built-in variables useful for CI/CD pipelines and test sharding, documented in **[`CHANGELOG.md`](https://github.com/mobile-dev-inc/Maestro/blob/main/CHANGELOG.md)** (line 21):

- **`MAESTRO_DEVICE_UDID`** – The unique device identifier of the device under test
- **`MAESTRO_SHARD_ID`** – The total number of shards when splitting flows across machines
- **`MAESTRO_SHARD_INDEX`** – The current shard index (0-based) for distributed execution

Access these directly in your YAML without any CLI configuration:

```yaml
- assertVisible: "Device UDID: ${MAESTRO_DEVICE_UDID}"
- assertVisible: "Shard ${MAESTRO_SHARD_INDEX} of ${MAESTRO_SHARD_ID}"

```

## Substitution Rules and Edge Cases

Understanding how Maestro processes the `${VAR}` syntax ensures reliable environment management:

- **Case-sensitivity** – Variable names are case-sensitive; `${username}` and `${USERNAME}` reference different values
- **Undefined variables** – If a referenced variable is undefined, the placeholder remains unchanged in the output and Maestro logs a warning, rather than failing silently or throwing an error
- **String applicability** – Substitution works for any string value within commands, including `inputText`, `tapOn` selectors, and `assertVisible` labels

## Summary

Managing environments in Maestro YAML relies on a three-layer architecture:

- **CLI injection** via `-e`/`--env` flags parsed in [`RunFlowTool.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/RunFlowTool.kt) for external secrets and configuration
- **Dynamic creation** via the `DefineVariables` command in [`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt) for runtime-generated values
- **Scope isolation** via `EnvironmentScope` in [`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt) to prevent variable leakage between sub-flows and parallel executions

Combine these mechanisms with built-in variables like `MAESTRO_DEVICE_UDID` to create flexible, secure, and scalable mobile UI testing pipelines.

## Frequently Asked Questions

### How do I pass environment variables when running a Maestro flow from the command line?

Use the `-e` or `--env` flag followed by a JSON object when executing `maestro run`. For example: `maestro run -e '{"API_KEY":"12345"}' flow.yml`. The CLI tool parses this JSON in [`RunFlowTool.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/RunFlowTool.kt) and injects the values into the flow's execution context, making them available via `${API_KEY}` syntax throughout the YAML file.

### What happens if I reference an undefined environment variable in Maestro YAML?

If you reference a variable that has not been defined via CLI, `DefineVariables`, or parent scope, Maestro leaves the `${VAR}` placeholder unchanged in the command string and logs a warning. The flow continues execution rather than failing immediately, allowing you to identify missing configuration without breaking the entire test suite.

### Can I override environment variables for specific sub-flows only?

Yes. When using the `runFlow` command, include an `env` block to specify variables that apply only to that sub-flow execution. These local overrides take precedence over CLI-provided values but do not affect the parent flow's environment or other parallel sub-flows, thanks to the isolated `EnvironmentScope` created in [`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt) for each sub-flow entry.

### Are environment variables shared between parallel Maestro flows?

No. Maestro enforces strict isolation between parallel flows through per-flow `EnvironmentScope` instances. As documented in the [`CHANGELOG.md`](https://github.com/mobile-dev-inc/Maestro/blob/main/CHANGELOG.md) regarding GraalJS isolation, variables set in one parallel execution branch cannot be accessed or modified by another, ensuring test reliability and preventing race conditions in concurrent test runs.