# How to Define Commands in Maestro YAML: Syntax, Structure, and Examples

> Learn how to define commands in Maestro YAML with clear syntax, structure, and examples. Explore the human-readable format and understand mapping to Kotlin data classes for automation.

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

---

**Maestro uses a human-readable YAML syntax where commands are defined as a list of automation actions separated from optional configuration by three hyphens (`---`), with each command mapping to a Kotlin data class in [`Commands.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Commands.kt) that implements the sealed `Command` interface.**

Maestro is an open-source mobile UI testing framework by mobile-dev-inc that allows developers to define test flows using declarative YAML files. When you define commands in Maestro YAML, you are essentially creating a sequence of instructions that the Maestro orchestrator converts into Kotlin data classes to drive automation on iOS and Android devices.

## Anatomy of a Maestro Flow File

Every Maestro flow consists of two distinct sections divided by the `---` separator. The optional configuration block sets up the test environment, while the required command list contains the actual automation steps.

### Configuration Block

The top section defines environment parameters such as `appId`, `url`, or `device`. These fields are optional but recommended for targeting specific applications.

### Command List

Below the separator, commands appear as a YAML array. Each entry represents a single automation action that maps to a specific implementation in the codebase.

```yaml
appId: com.example.myapp
---
- launchApp
- tapOn: "Login"
- assertVisible:
    selector: "Welcome"
    timeout: "10s"

```

## How YAML Commands Map to the Codebase

When Maestro parses a flow, the YAML command reader (tested in `YamlCommandReaderTest` under `maestro-orchestra/src/test/resources`) converts each list item into an instance of a command data class. All command definitions reside 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)**.

### The Command Interface

Every command implements the sealed `Command` interface:

```kotlin
sealed interface Command {
    @get:JsonIgnore val originalDescription: String
    fun description(): String = label ?: originalDescription
    fun evaluateScripts(jsEngine: JsEngine): Command
    fun visible(): Boolean = true
    val label: String?
    val optional: Boolean
}

```

Key properties include:

- **`label`**: Provides a human-readable name for CLI output and Maestro Studio visualization
- **`optional`**: When set to `true`, allows the flow to continue with a warning status if the command fails (see CHANGELOG line 312)

### Common Command Mappings

Specific YAML entries correspond to distinct Kotlin classes in [`Commands.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Commands.kt):

- `launchApp` → `LaunchAppCommand`
- `tapOn` → `TapOnCommand` (fields: `selector`, `waitToSettleTimeoutMs`)
- `assertVisible` → `AssertVisibleCommand` (fields: `selector`, `timeout`)
- `scrollUntilVisible` → `ScrollUntilVisibleCommand` (fields: `direction`, `visibilityPercentage`)
- `swipe` → `SwipeCommand` (fields: `startPoint`, `endPoint`, `duration`)

## Supported Command Syntax Forms

Maestro supports multiple YAML syntax patterns depending on command complexity:

1. **No parameters**: `- launchApp` uses default values defined in the data class
2. **Scalar argument**: `- tapOn: "Accept"` passes a single string value
3. **Map of arguments**: Complex commands accept key-value pairs for precise control
4. **Universal fields**: Any command can include `label` and `optional` fields for metadata and error handling

## Practical Code Examples

### Basic Authentication Flow

```yaml
appId: com.example.myapp
---
- launchApp
- tapOn: "Login"
- inputText: "user@example.com"
- tapOn: "Submit"
- assertVisible:
    selector: "Welcome"
    timeout: "8s"

```

### Optional Commands with Labels

Use `label` for documentation and `optional: true` to handle intermittent UI elements:

```yaml
url: https://example.com
---
- launchApp:
    label: "Open the website"
- tapOn:
    selector: "Accept Cookies"
    optional: true
    label: "Handle cookie banner"
- scrollUntilVisible:
    selector: "Feature Section"
    direction: down
    visibilityPercentage: 80
    timeout: "12s"

```

### Composite Commands and Subflows

The `subflow` command is a **CompositeCommand** that nests another YAML file:

```yaml
appId: com.example.myapp
---
- launchApp
- subflow:
    flow: "login_subflow.yaml"
    label: "Perform login"

```

### Advanced Swipe Configuration

```yaml
appId: com.example.myapp
---
- launchApp
- swipe:
    direction: left
    startRelative: "center"
    endRelative: "leftEdge"
    duration: 600
    waitToSettleTimeoutMs: 1500
    label: "Swipe left on carousel"

```

## Extending Maestro's Command Set

To add new functionality to Maestro, developers must:

1. Create a data class in [`Commands.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Commands.kt) implementing `Command` (or `CompositeCommand` for nested flows)
2. Register it in the `MaestroCommand` sealed hierarchy
3. Add test cases in `YamlCommandReaderTest` to verify YAML parsing

According to the contribution guide in [`CONTRIBUTING.md`](https://github.com/mobile-dev-inc/Maestro/blob/main/CONTRIBUTING.md), this workflow ensures new commands integrate properly with the orchestrator's command evaluation engine.

## Summary

- Maestro flows use a two-part structure: an optional configuration header and a command list separated by `---`
- Each YAML command maps to a Kotlin data class in [`Commands.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Commands.kt) implementing the `Command` interface
- The `label` field provides human-readable descriptions for debugging, while `optional: true` enables non-critical path execution
- Commands support scalar, map, and composite syntax forms depending on required parameters
- New commands require implementation in `maestro-orchestra-models` and registration in the sealed class hierarchy

## Frequently Asked Questions

### What is the purpose of the `---` separator in Maestro YAML?

The three-hyphen separator divides the optional configuration block (containing `appId`, `url`, etc.) from the required command list. This distinction allows Maestro to initialize the test environment before executing the sequence of automation actions defined below the separator.

### How do I make a command optional so the flow continues if it fails?

Add `optional: true` to any command's parameter map. According to the source code in [`Commands.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Commands.kt), this property marks the command as non-critical, causing Maestro to log a warning instead of failing the entire flow when the command cannot execute successfully.

### Where are command definitions stored in the Maestro codebase?

All command data classes reside 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)**. This file contains the sealed `Command` interface and implementations like `TapOnCommand`, `AssertVisibleCommand`, and `SwipeCommand` that correspond to YAML directives.

### Can I add custom labels to commands for better logging?

Yes. Every command accepts a `label` field that overrides the default description in CLI output and Maestro Studio. As implemented in the `Command` interface's `description()` method, the label value takes precedence over `originalDescription` when generating execution logs.