# How to define subcommands for a \`@main\` command in a Julia app?

**URL:** https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153
**Category:** General Usage
**Tags:** question, cli, command-line-options
**Created:** [November 26, 2025, 9:40pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153 "2025-11-26T21:40:50Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![singularitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/singularitti/32/17678_2.png) [@singularitti](https://discourse.julialang.org/u/singularitti)
#### Post date: [November 26, 2025, 9:40pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/1 "2025-11-26T21:40:50Z")

</div>

Since Julia now allows us to install an app/command to call it from the Shell by the `@main` function, I wonder how to mimic cases like some commands having subcommands in `@main`? Like `git add`, `git status`, `git clone` for `git`. That is, those subcommands should be gathered under a global name.

A possible solution I can think of is the check the second argument of `ARGS`, e.g., if it is a specific subcommand. I am not sure this is the correct way.

I was using [Comonicon.jl](https://github.com/comonicon/Comonicon.jl), which allows subcommands as long as they are defined in submodules of a package.

---

<div class="post-metadata">

### Author: ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)
#### Post date: [November 26, 2025, 10:32pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/2 "2025-11-26T22:32:30Z")

</div>

I am not sure if I understand what you mean with subcommands but `DocOpt.jl` implements the infamous docopt concept (awesome talk by Vladimir Keleshev btw. [https://youtu.be/pXhcPJK5cMc](https://youtu.be/pXhcPJK5cMc)) and you can define a command line interface (CLI) pretty naturally: [GitHub - docopt/DocOpt.jl: command line arguments parser](https://github.com/docopt/DocOpt.jl)

One of the first slides shows this CLI

 ![Screenshot 2025-11-26 at 23.29.18](https://global.discourse-cdn.com/julialang/original/3X/5/d/5d95b684db6c333d7f3a818762fa5fcd15541a71.png)

and I assume the “subcommand” you are referring to would be something like `tcp` or `serial` in that example.

You would then need to parse the arguments and call the appropriate function.

If you however mean something like dedicated functions for different “subcommands”, like e.g. what [Click for Python](https://click.palletsprojects.com/en/stable/) implements, I think there is nothing comparable (yet) in the Julia ecosystem. Note that Click provides type checking and direct mapping of functions to the CLI. Might be an overkill in some cases 😉

---

<div class="post-metadata">

### Author: ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)
#### Post date: [November 26, 2025, 10:49pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/3 "2025-11-26T22:49:58Z")

</div>

> [@singularitti](#):
>
> A possible solution I can think of is the check the second argument of `ARGS`, e.g., if it is a specific subcommand. I am not sure this is the correct way.

The subcommand should be the first argument not the second argument, since the main script name does not appear at the beginning of ARGS.

Then you can ask the function corresponding to the subcommand to parse ARGS[2:end]. For example, ArgParse.jl can parse a user-supplied argument list.

---

<div class="post-metadata">

### Author: ![singularitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/singularitti/32/17678_2.png) [@singularitti](https://discourse.julialang.org/u/singularitti)
#### Post date: [December 1, 2025, 11:20pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/4 "2025-12-01T23:20:03Z")

</div>

Thanks. I mean something like `docopt`, but this was already implemented in [Comonicon.jl](https://github.com/comonicon/Comonicon.jl). I was wondering if I can do that directly with `ARGS`.

---

<div class="post-metadata">

### Author: ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)
#### Post date: [December 2, 2025, 10:39am UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/5 "2025-12-02T10:39:30Z")

</div>

No, `ARGS` is just a lightweight dictionary 😉

---

<div class="post-metadata">

### Author: ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)
#### Post date: [December 2, 2025, 7:46pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/6 "2025-12-02T19:46:07Z")

</div>

> [@singularitti](#):
>
> I was wondering if I can do that directly with `ARGS`.

Of course, the package you mentioned is essentially a nice package for parsing `ARGS`. You could cook up your own version if you want.

---

<div class="post-metadata">

### Author: ![andreeco](https://avatars.discourse-cdn.com/v4/letter/a/d26b3c/32.png) [@andreeco](https://discourse.julialang.org/u/andreeco)
#### Post date: [December 2, 2025, 9:35pm UTC](https://discourse.julialang.org/t/how-to-define-subcommands-for-a-main-command-in-a-julia-app/134153/7 "2025-12-02T21:35:59Z")

</div>

Maybe [this example](https://discourse.julialang.org/t/trim-examples/133818) can help you to start with:

```julia
module SimpleCalcProject

using TOML

const version = TOML.parsefile("Project.toml")["version"]

function print_help()
    println(Core.stdout,
        """
SimpleCalcProject CLI
Usage:
  simplecalc <command> [args...]

Commands:
  add <a> <b> Add two numbers
  sub <a> <b> Subtract second number from first
  sum <nums...> Sum all numbers (sum)

Options:
  -h, --help Show this help message
  -v, --version Show version information
""")
end

function print_version()
    println(Core.stdout, "SimpleCalcProject CLI v$(version)")
end

function parse_numbers(remaining)
    try
        tryparse.(Float64, remaining)::Vector{Float64}
    catch 
        println(Core.stdout, "Error: invalid argument(s)")
        Float64[]
    end
end

function print_calculation(cmd, numbers)
    if cmd == "add" && length(numbers) == 2
        println(Core.stdout, "Result: $(numbers[1] + numbers[2])")
    elseif cmd == "sub" && length(numbers) == 2
        println(Core.stdout, "Result: $(numbers[1] - numbers[2])")
    elseif cmd == "sum" && !isempty(numbers)
        println(Core.stdout, "Result: $(sum(numbers))")
    else
        println(Core.stdout, "Invalid command or arguments. Try --help.")
    end
end

function cli(args)

    if isempty(args)
        print_help()
        return 0
    end

    cmd = args[1]

    if cmd in ["--help", "-h"]
        print_help()
        return 0
    elseif cmd in ["--version", "-v"]
        print_version()
        return 0
    end
    
    remaining = args[2:end]
    
    numbers = parse_numbers(remaining)
    
    length(numbers) == 0 && return 1
    
    print_calculation(cmd, numbers)
    
    return 0
end

function @main(ARGS)
    return cli(ARGS)
end

end

```
