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

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. 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 (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 package to represent your command’s parameters. For example, to create a showToast command:

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. This automatically registers it in the objectCommands collection because Jackson introspects the primary constructor:

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

"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 (the large when block) and add a branch that converts your YAML DTO into a concrete MaestroCommand:

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

You must also create the corresponding command execution class (e.g., 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:

./gradlew :maestro-orchestra:assemble

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

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: 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: 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, 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 and 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. 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) that implements the MaestroCommand interface. Then reference this class in the _toCommands method of 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 and 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.

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 →