Sub (sub) Commands in Clap
May 15, 2026
I found it confusing at first to think about how to use clap to do a “sub- sub- command”. We opted to use clap’s “derive” method to define our interface.
Part of that looks like: mcap encrypt and mcap decrypt. This sort of “single depth” subcommand is a popular pattern.
I wanted to add a “debug” subcommand which itself had a series of subcommands – not really intended for “normal” use.
The Source Code
Lets make a minimal example. Starting with cargo init and cargo add clap --features derive gives us a repository producing an executable.
In our src/main.rs we set up an example:
use clap::{Parser, Subcommand};
use clap::Error;
#[derive(Parser)]
#[command(version = "25.12.1")]
#[command(about = "Example sub-sub-commands in clap")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
#[command(arg_required_else_help(true))]
#[command(about = "I am a subcommand with subcommands")]
enum DebugCommands {
#[command(about = "A sub-sub-command to greet you")]
Hello {
name: String,
}
}
// the "first layer" or "top level" subcommands
#[derive(Subcommand)]
#[command(arg_required_else_help(true))]
enum Commands {
#[command(about = "frobnicate the appropriate quux")]
Frob {
quux: String,
},
#[command(about = "Debugging tools.")]
Debug {
#[command(subcommand)]
command: Option<DebugCommands>,
},
}
fn main_debug_hello(whom: &String) -> Result<(), Error> {
println!("Hello, {}", whom);
Ok(())
}
fn main_frob(quux: &String) -> Result<(), Error> {
println!("Frobnicate: {}", quux);
Ok(())
}
fn main() {
let cli = Cli::parse();
let result = match &cli.command {
Some(Commands::Frob {
quux,
}) => main_frob(quux),
Some(Commands::Debug { command }) => match command {
Some(DebugCommands::Hello{ name }) => main_debug_hello(name),
None => Ok(()),
}
None => Ok(()),
};
if let Err(e) = result {
println!("Error: {}", e);
std::process::exit(2);
}
}
That’s kind of a lot but gives us a simple binary with one “normal” sub-command frob and a second sub-command debug that itself has sub-commands. In this case there’s only one sub-sub command (debug hello <name>).
If all is aligned, we can run the above with cargo run. This should ultimately produce output like:
Example sub-sub-commands in clap
Usage: clap-subsub [COMMAND]
Commands:
frob frobnicate the appropriate quux
debug Debugging tools.
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
The Sub-Sub Command
Basically “just” a matter of aligning the macros correctly. Hopefully at least future-meejah can gain insight.
Running the debug subcommand produces some useful help text, as desired:
$ cargo run -- debug
Debugging tools.
Usage: clap-subsub debug [COMMAND]
Commands:
hello A sub-sub-command to greet you
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
txtorcon
carml
cuv’ner