How to define commands, options, and arguments in System.CommandLine

Important

System.CommandLine is currently in PREVIEW, and this documentation is for version 2.0 beta 4. Some information relates to prerelease product that may be substantially modified before it's released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

This article explains how to define commands, options, and arguments in command-line apps that are built with the System.CommandLine library. To build a complete application that illustrates these techniques, see the tutorial Get started with System.CommandLine.

For guidance on how to design a command-line app's commands, options, and arguments, see Design guidance.

Define a root command

Every command-line app has a root command, which refers to the executable file itself. The simplest case for invoking your code, if you have an app with no subcommands, options, or arguments, would look like this:

using System.CommandLine;

class Program
{
    static async Task Main(string[] args)
    {
        var rootCommand = new RootCommand("Sample command-line app");

        rootCommand.SetHandler(() =>
        {
            Console.WriteLine("Hello world!");
        });

        await rootCommand.InvokeAsync(args);
    }
}

Define subcommands

Commands can have child commands, known as subcommands or verbs, and they can nest as many levels as you need. You can add subcommands as shown in the following example:

var rootCommand = new RootCommand();
var sub1Command = new Command("sub1", "First-level subcommand");
rootCommand.Add(sub1Command);
var sub1aCommand = new Command("sub1a", "Second level subcommand");
sub1Command.Add(sub1aCommand);

The innermost subcommand in this example can be invoked like this:

myapp sub1 sub1a

Define options

A command handler method typically has parameters, and the values can come from command-line options. The following example creates two options and adds them to the root command. The option names include double-hyphen prefixes, which is typical for POSIX CLIs. The command handler code displays the values of those options:

var delayOption = new Option<int>
    (name: "--delay",
    description: "An option whose argument is parsed as an int.",
    getDefaultValue: () => 42);
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);

rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
        Console.WriteLine($"--message = {messageOptionValue}");
    },
    delayOption, messageOption);

Here's an example of command-line input and the resulting output for the preceding example code:

myapp --delay 21 --message "Hello world!"
--delay = 21
--message = Hello world!

Global options

To add an option to one command at a time, use the Add or AddOption method as shown in the preceding example. To add an option to a command and recursively to all of its subcommands, use the AddGlobalOption method, as shown in the following example:

var delayOption = new Option<int>
    ("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.AddGlobalOption(delayOption);
rootCommand.Add(messageOption);

var subCommand1 = new Command("sub1", "First level subcommand");
rootCommand.Add(subCommand1);

var subCommand1a = new Command("sub1a", "Second level subcommand");
subCommand1.Add(subCommand1a);

subCommand1a.SetHandler((delayOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
    },
    delayOption);

await rootCommand.InvokeAsync(args);

The preceding code adds --delay as a global option to the root command, and it's available in the handler for subCommand1a.

Define arguments

Arguments are defined and added to commands like options. The following example is like the options example, but it defines arguments instead of options:

var delayArgument = new Argument<int>
    (name: "delay",
    description: "An argument that is parsed as an int.",
    getDefaultValue: () => 42);
var messageArgument = new Argument<string>
    ("message", "An argument that is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayArgument);
rootCommand.Add(messageArgument);

rootCommand.SetHandler((delayArgumentValue, messageArgumentValue) =>
    {
        Console.WriteLine($"<delay> argument = {delayArgumentValue}");
        Console.WriteLine($"<message> argument = {messageArgumentValue}");
    },
    delayArgument, messageArgument);

await rootCommand.InvokeAsync(args);

Here's an example of command-line input and the resulting output for the preceding example code:

myapp 42 "Hello world!"
<delay> argument = 42
<message> argument = Hello world!

An argument that is defined without a default value, such as messageArgument in the preceding example, is treated as a required argument. An error message is displayed, and the command handler isn't called, if a required argument isn't provided.

Define aliases

Both commands and options support aliases. You can add an alias to an option by calling AddAlias:

var option = new Option("--framework");
option.AddAlias("-f");

Given this alias, the following command lines are equivalent:

myapp -f net6.0
myapp --framework net6.0

Command aliases work the same way.

var command = new Command("serialize");
command.AddAlias("serialise");

This code makes the following command lines equivalent:

myapp serialize
myapp serialise

We recommend that you minimize the number of option aliases that you define, and avoid defining certain aliases in particular. For more information, see Short-form aliases.

Required options

To make an option required, set its IsRequired property to true, as shown in the following example:

var endpointOption = new Option<Uri>("--endpoint") { IsRequired = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

The options section of the command help indicates the option is required:

Options:
  --endpoint <uri> (REQUIRED)
  --version               Show version information
  -?, -h, --help          Show help and usage information

If the command line for this example app doesn't include --endpoint, an error message is displayed and the command handler isn't called:

Option '--endpoint' is required.

If a required option has a default value, the option doesn't have to be specified on the command line. In that case, the default value provides the required option value.

Hidden commands, options, and arguments

You might want to support a command, option, or argument, but avoid making it easy to discover. For example, it might be a deprecated or administrative or preview feature. Use the IsHidden property to prevent users from discovering such features by using tab completion or help, as shown in the following example:

var endpointOption = new Option<Uri>("--endpoint") { IsHidden = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

The options section of this example's command help omits the --endpoint option.

Options:
  --version               Show version information
  -?, -h, --help          Show help and usage information

Set argument arity

You can explicitly set argument arity by using the Arity property, but in most cases that is not necessary. System.CommandLine automatically determines the argument arity based on the argument type:

Argument type Default arity
Boolean ArgumentArity.ZeroOrOne
Collection types ArgumentArity.ZeroOrMore
Everything else ArgumentArity.ExactlyOne

Multiple arguments

By default, when you call a command, you can repeat an option name to specify multiple arguments for an option that has maximum arity greater than one.

myapp --items one --items two --items three

To allow multiple arguments without repeating the option name, set Option.AllowMultipleArgumentsPerToken to true. This setting lets you enter the following command line.

myapp --items one two three

The same setting has a different effect if maximum argument arity is 1. It allows you to repeat an option but takes only the last value on the line. In the following example, the value three would be passed to the app.

myapp --item one --item two --item three

List valid argument values

To specify a list of valid values for an option or argument, specify an enum as the option type or use FromAmong, as shown in the following example:

var languageOption = new Option<string>(
    "--language",
    "An option that that must be one of the values of a static list.")
        .FromAmong(
            "csharp",
            "fsharp",
            "vb",
            "pwsh",
            "sql");

Here's an example of command-line input and the resulting output for the preceding example code:

myapp --language not-a-language
Argument 'not-a-language' not recognized. Must be one of:
        'csharp'
        'fsharp'
        'vb'
        'pwsh'
        'sql'

The options section of command help shows the valid values:

Options:
  --language <csharp|fsharp|vb|pwsh|sql>  An option that must be one of the values of a static list.
  --version                               Show version information
  -?, -h, --help                          Show help and usage information

Option and argument validation

For information about argument validation and how to customize it, see the following sections in the Parameter binding article:

See also