# How to Customize YAML Flow Parsing in Maestro: A Complete Developer’s Guide

> Customize YAML flow parsing in Maestro by modifying and recompiling the open-source maestro orchestra library. Get the complete developer guide.

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

---

**Maestro does not provide a runtime plugin mechanism for YAML parsing; customizing flow syntax requires modifying the open-source `maestro-orchestra` source code and recompiling the library.**

Maestro is a popular open-source mobile UI testing framework that interprets YAML files to automate user interactions. While the framework ships with dozens of built-in commands, teams with specialized testing needs often want to customize YAML flow parsing in Maestro to support proprietary operations or simplify repetitive test patterns. Because the command vocabulary is statically compiled into the parser, extending Maestro requires a deep dive into its Jackson-based deserialization architecture.

## How Maestro Parses YAML Flow Files

The entry point for all YAML flow parsing is `MaestroFlowParser` located in [`maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt). This class constructs a Jackson `ObjectMapper` and registers a custom `YamlCommandDeserializer` that inspects each YAML token to determine which command class to instantiate.

### The Two Command Registries

During deserialization, the parser categorizes every potential command into one of two hard-coded lookup tables defined at compile time:

- **`stringCommands`**: Defined in [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/MaestroFlowParser.kt) (lines 59‑84), this map handles **scalar YAML tokens**—commands that can be expressed as simple strings without parameters, such as `launchApp` when used without arguments.

- **`objectCommands`**: Automatically derived from the primary constructor of `YamlFluentCommand` (lines 54‑57), this collection handles **mapping tokens**—commands that require a map of key-value pairs, such as `tapOn` with selector options.

When the deserializer encounters a YAML scalar, it invokes `parseStringCommand` against the `stringCommands` map. When it encounters an object (a YAML mapping), it invokes `parseObjectCommand` against the `objectCommands` map. If the command name is absent from both collections, the parser throws a `ParseException` with an "Invalid Command" error and halts execution.

## Extending Maestro with Custom Commands

Because both registries are **hard-coded at compile time**, you cannot add commands via configuration files or environment variables. You must extend the source code following these five steps:

### Step 1: Define the YAML DTO

Create a new data class in the [`maestro.orchestra.yaml`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro.orchestra.yaml) package to represent your command’s parameters. For example, to create a `showToast` command:

```kotlin
package maestro.orchestra.yaml

import com.fasterxml.jackson.annotation.JsonProperty

data class YamlShowToast(
    @JsonProperty("message") val message: String,
    @JsonProperty("duration") val duration: Long? = null,
)

```

### Step 2: Extend the Central Command DTO

Add your new DTO as a nullable property to [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt). This automatically registers it in the `objectCommands` collection because Jackson introspects the primary constructor:

```kotlin
// Inside YamlFluentCommand.kt data class definition
val showToast: YamlShowToast? = null,

```

### Step 3: Register String Commands (Optional)

If your command requires no arguments and should work as a scalar string (e.g., `- restartApp`), manually add an entry to the `stringCommands` map in [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/MaestroFlowParser.kt):

```kotlin
"restartApp" to { location -> YamlFluentCommand(
    _location = location,
    launchApp = YamlLaunchApp(
        appId = null,
        clearState = true  // Forces a full restart
    )
)}

```

Object commands with parameters skip this step; they are automatically available once added to `YamlFluentCommand`.

### Step 4: Implement Command Conversion

Navigate to the `_toCommands` method inside [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) (the large `when` block) and add a branch that converts your YAML DTO into a concrete `MaestroCommand`:

```kotlin
showToast != null -> listOf(
    MaestroCommand(
        ShowToastCommand(
            message = showToast.message,
            duration = showToast.duration
        )
    )
)

```

You must also create the corresponding command execution class (e.g., [`ShowToastCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/ShowToastCommand.kt)) in the `maestro.orchestra.command` package to define the runtime behavior.

### Step 5: Rebuild the Library

Compile your changes to generate the updated CLI and libraries:

```bash
./gradlew :maestro-orchestra:assemble

```

After rebuilding, your custom syntax is immediately available in flow files:

```yaml
appId: com.example.myapp
---
- showToast:
    message: "Custom command active"
    duration: 3000

```

## Critical Source Files for Customization

Understanding the following files is essential when you customize YAML flow parsing in Maestro:

- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt)**: Contains the Jackson configuration, the `stringCommands` map, and the `YamlCommandDeserializer` implementation. This is where command name resolution happens.

- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt)**: The central DTO that aggregates all possible YAML commands. Its constructor defines the `objectCommands` lookup table, and its `_toCommands` method handles the conversion from YAML representation to executable `MaestroCommand` objects.

- **`maestro-orchestra/src/main/kotlin/maestro/orchestra/yaml/Yaml*.kt`**: Package containing individual command DTOs (e.g., [`YamlTapOnElement.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlTapOnElement.kt), [`YamlLaunchApp.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlLaunchApp.kt)). Create new files here following the existing naming convention.

- **`maestro-orchestra/src/main/java/maestro/orchestra/command/`**: Directory containing concrete command implementations that perform actual device interactions. Any new YAML command requires a corresponding class here to handle execution logic.

## Summary

- **Maestro’s YAML parser is static**: Command vocabularies are hard-coded in [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/MaestroFlowParser.kt) and [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) at compile time.
- **No runtime plugins**: You cannot add commands via external configuration; source modification and recompilation are mandatory.
- **Two registration paths**: Use `stringCommands` for scalar shortcuts and extend `YamlFluentCommand`’s constructor for object-based commands with parameters.
- **Conversion is manual**: Every new YAML DTO requires an explicit branch in `YamlFluentCommand._toCommands` to map it to an executable command class.
- **Rebuild required**: Changes take effect only after recompiling the `maestro-orchestra` module or the entire project.

## Frequently Asked Questions

### Can I add custom Maestro commands without modifying the source code?

No. As implemented in `mobile-dev-inc/Maestro`, the `stringCommands` and `objectCommands` lookup tables are immutable after compilation. There is no configuration file, environment variable, or dynamic classloader mechanism that allows runtime command registration. You must fork the repository, implement your changes, and rebuild the binary.

### What is the difference between stringCommands and objectCommands in Maestro?

**`stringCommands`** handles YAML scalars—simple command names without parameters (e.g., `- back`). It is explicitly defined as a map in [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/MaestroFlowParser.kt). **`objectCommands`** handles YAML mappings—commands with key-value configurations (e.g., `- tapOn: { id: "button" }`). It is implicitly built from the primary constructor properties of `YamlFluentCommand` via Jackson’s introspection.

### Where should I implement the actual execution logic for my custom command?

The YAML parsing layer only converts text into data objects. To implement behavior, create a new class in `maestro-orchestra/src/main/java/maestro/orchestra/command/` (e.g., [`ShowToastCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/ShowToastCommand.kt)) that implements the `MaestroCommand` interface. Then reference this class in the `_toCommands` method of [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) to bridge the parser with the executor.

### Will my custom YAML commands break when I update Maestro?

Yes, if you simply upgrade the CLI binary without re-applying your patches. Because you are modifying core parser files like [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/MaestroFlowParser.kt) and [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt), you must maintain a fork or patch set. When updating, you need to rebase your custom command implementations onto the latest upstream version and resolve any merge conflicts in the command registries or conversion logic.