Skip to content

Options

Options are added to commands by defining a property delegate with the option function.

Basic Options

The default option takes one value of type String. The property is nullable. If the option is not given on the command line, the property value will be null. If the option is given at least once, the property will return the value of the last occurrence of the option.

class Hello: CliktCommand() {
    val name by option(help="your name")
    override fun run() {
        echo("Hello, $name!")
    }
}
$ ./hello --name=Foo
Hello, Foo!

Option Names

If you don’t specify names for an option, a lowercase hyphen-separated name is automatically inferred from the property. For example, val myOpt by option() will create an option that can be called with --my-opt.

You can also specify any number of names for an option manually:

class Hello: CliktCommand() {
    val name by option("-n", "--name", help="your name")
    override fun run() {
        echo("Hello, $name!")
    }
}

Option names that are two characters long (like -n) are treated as POSIX-style short options. You call them with a value like this:

$ ./hello -nfoo
Hello, foo!
$ ./hello -n foo
Hello, foo!

All other option names are considered long options, and can be called like this:

$ ./hello --name=foo
Hello, foo!
$ ./hello --name foo
Hello, foo!

Customizing Options

The option behavior and delegate type can be customized by calling extension functions on the option call. For example, here are some different option declarations:

val a: String? by option()
val b: Int? by option().int()
val c: Pair<Int, Int>? by option().int().pair()
val d: Pair<Int, Int> by option().int().pair().default(0 to 0)
val e: Pair<Float, Float> by option().float().pair().default(0f to 0f)

There are three main types of behavior that can be customized independently:

  1. The type of each value in the option. The value type is String by default, but can be customized with built-in functions like int or choice, or manually with convert. This is detailed in the parameters page.
  2. The number of values that the option requires. Options take one value by default, but this can be changed with built-in functions like pair and triple, or manually with transformValues.
  3. How to handle all calls to the option (i.e. if the option is not present, or is present more than once). By default, the option delegate value is the null if the option is not given on the command line, and will use the value of the last occurrence if the option is given more than once. You can change this behavior with functions like default and multiple.

Since the three types of customizations are orthogonal, you can choose which ones you want to use, and if you implement a new customization, it can be used with all the existing functions without any repeated code.

Default Values

By default, option delegates return null if the option wasn’t provided on the command line. You can instead return a default value with default.

class Pow : CliktCommand() {
    val exp by option("-e", "--exp").double().default(1.0)
    override fun run() {
        echo("2 ^ $exp = ${(2.0).pow(exp)}")
    }
}
$ ./pow -e 8
2 ^ 8.0 = 256.0
$ ./pow
2 ^ 1.0 = 2.0

If the default value is expensive to compute, or you want to use another parameter as the default, you can use defaultLazy instead of default. It has the same effect, but you give it a lambda returning the default value, and the lambda will only be called if the option isn’t present on the command line.

Warning

The lambda you pass to defaultLazy is normally called at most once. But the lambda references another parameter, it may be called more than once in order to make sure the parameter it references is available.

Multi Value Options

Options can take a fixed number of values separated by whitespace, a variable number of values separated by a delimiter, or a variable number of values separated by a whitespace.

Options With Fixed Number of Values

There are built in functions for options that take two values (pair, which uses a Pair), or three values (triple, which uses a Triple). You can change the type of each value as normal with functions like int.

If you need more values, you can provide your own container with transformValues. You give that function the number of values you want, and a lambda that will transform a list of values into the output container. The list will always have a size equal to the number you specify. If the user provides a different number of values, Clikt will inform the user and your lambda won’t be called.

data class Quad<out T>(val a: T, val b: T, val c: T, val d: T)
fun <T> Quad<T>.toList(): List<T> = listOf(a, b, c, d)

class Geometry : CliktCommand() {
    val square by option().int().pair()
    val cube by option().int().triple()
    val tesseract by option().int().transformValues(4) { Quad(it[0], it[1], it[2], it[3]) }
    override fun run() {
        echo("Square has dimensions ${square?.toList()?.joinToString("x")}")
        echo("Cube has dimensions ${cube?.toList()?.joinToString("x")}")
        echo("Tesseract has dimensions ${tesseract?.toList()?.joinToString("x")}")
    }
}
$ ./geometry --square 1 2 --cube 3 4 5 --tesseract 6 7 8 9
Square has dimensions 1x2
Cube has dimensions 3x4x5
Tesseract has dimensions 6x7x8x9

Splitting an Option Value on a Delimiter

You can use split to allow a variable number of values to a single option invocation by separating the values with non-whitespace delimiters. This will also split values from environment variables.

class C : CliktCommand() {
    val profiles by option("-P", envvar="PROFILES").split(",")
    override fun run() {
        for (profile in profiles) {
            echo(profile)
        }
    }
}
$ ./split -P profile-1,profile-2
profile-1
profile-2
$ export PROFILES=profile-1,profile-2
$ ./split
profile-1
profile-2

Options with an Optional Value

You can create options that take zero or one values with optionalValue or optionalValueLazy.

class C : CliktCommand() {
    val log by option().optionalValue("debug").default("none")
    override fun run() {
        echo("log level: $log")
    }
}
$ ./command --log
log level: debug
$ ./command --log=verbose
log level: verbose
$ ./command
log level: none

Warning

If a user specifies the value without an =, as in --log debug, the debug will always be interpreted as a value for the option, even if the command accepts arguments. This might be confusing to your users, so you can require that the option value always be specified with = by passing optionalValue(acceptsUnattachedValue=false).

Options With a Variable Number of Values

If you want your option to take a variable number of values, but want to split the value on whitespace rather than a delimiter, you can use varargValues.

class Order : CliktCommand() {
    val sizes: List<String> by option().varargValues()
    override fun run() {
        echo("You ordered: $sizes")
    }
}
$ ./order --sizes small medium
You ordered: ["small", "medium"]

By default, varargValues requires at least one value, and has no maximum limit. You can configure the limits by passing min and max arguments to varargValues.

Multiple Options

Normally, when an option is provided on the command line more than once, only the values from the last occurrence are used. But sometimes you want to keep all values provided. For example, git commit -m foo -m bar would create a commit message with two lines: foo and bar. To get this behavior with Clikt, you can use multiple. This will cause the property delegate value to be a list, where each item in the list is the value of from one occurrence of the option. If the option is never given, the list will be empty (or you can specify a default to use).

class Commit : CliktCommand() {
    val message: List<String> by option("-m").multiple()
    override fun run() {
        echo(message.joinToString("\n"))
    }
}
$ ./commit -m foo -m bar
foo
bar

You can combine multiple with item type conversions and multiple values.

val opt: List<Pair<Int, Int>> by option().int().pair().multiple()

Default values for option().multiple()

You can also supply a default value to multiple or require at least one value be present on the command line. These are specified as arguments rather than with separate extension functions since they don’t change the type of the delegate.

val opt: List<String> by option().multiple(required=true)
val opt: List<String> by option().multiple(default=listOf("default message"))

Deduplicating option().multiple() into a unique set

You can discard duplicate values from a multiple option with unique.

class Build : CliktCommand() {
    val platforms: Set<String> by option("-p").multiple().unique()
    override fun run() {
        echo("Building for platforms: $platforms")
    }
}
$ ./build -p android -p ios -p android
Building for platforms: [android, ios]

Key-Value and Map Options

You can split an option’s value into a key-value pair with splitPair. By default, the delimiter = will be used to split. You can also use associate to allow the option to be specified multiple times, and have its values collected in a map.

class Build : CliktCommand() {
    val systemProp: Map<String, String> by option("-D", "--system-prop").associate()

    override fun run() {
        echo(systemProp)
    }
}
$ ./build -Dplace=here --system-prop size=small
{place=here, size=small}

Boolean Flag Options

Flags are options that don’t take a value. Boolean flags can be enabled or disabled, depending on the name used to invoke the option. You can turn an option into a boolean flag with flag. That function takes an optional list of secondary names that will be added to any existing or inferred names for the option. If the option is invoked with one of the secondary names, the delegate will return false. It’s a good idea to always set secondary names so that a user can disable the flag if it was enabled previously.

class Cli : CliktCommand() {
    val flag by option("--on", "-o").flag("--off", "-O", default = false)
    override fun run() {
        echo(flag)
    }
}
$ ./cli -o
true
$ ./cli --on --off
false

Multiple short flag options can be combined when called on the command line:

class Cli : CliktCommand() {
    val flagA by option("-a").flag()
    val flagB by option("-b").flag()
    val foo by option("-f")
    override fun run() {
        echo("$flagA $flagB $foo")
    }
}
$ ./cli -abfFoo
true true Foo

Tip

You can diasable short option grouping by setting Context.allowGroupedShortOptions to false.

Counted Flag Options

You might want a flag option that counts the number of times it occurs on the command line. You can use counted for this.

You can specify a limit for the number of times the counted option can be given, and either clamp the value or show an error if the limit is exceeded.

class Log : CliktCommand() {
    val verbosity by option("-v").counted(limit=3, clamp=true)
    override fun run() {
        echo("Verbosity level: $verbosity")
    }
}
$ ./log -vvv
Verbosity level: 3

Feature Switch Flags

Another way to use flags is to assign a value to each option name. You can do this with switch, which takes a map of option names to values. Note that the names in the map replace any previously specified or inferred names.

class Size : CliktCommand() {
    val size by option().switch(
        "--large" to "large",
        "--small" to "small"
    ).default("unknown")
    override fun run() {
        echo("You picked size $size")
    }
}
$ ./size --small
You picked size small

Choice Options

You can restrict the values that a regular option can take to a set of values using choice. You can also map the input values to new types.

class Digest : CliktCommand() {
    val hash by option().choice("md5", "sha1")
    override fun run() {
        echo(hash)
    }
}
$ ./digest --hash=md5
md5
$ ./digest --hash=sha256
Usage: digest [<options>]

Error: Invalid value for "--hash": invalid choice: sha256. (choose from md5, sha1)
$ ./digest --help
Usage: digest [<options>]

Options:
  --hash [md5|sha1]
  -h, --help         Show this message and exit

Mutually Exclusive Option Groups

If choice or switch options aren’t flexible enough, you can use mutuallyExclusiveOptions to group any nullable options into a mutually exclusive group. If more than one of the options in the group is given on the command line, the last value is used.

If you want different types for each option, you can wrap them in a sealed class.

sealed class Fruit {
    data class Oranges(val size: String): Fruit()
    data class Apples(val count: Int): Fruit()
}
class Order : CliktCommand() {
    val fruit: Fruit? by mutuallyExclusiveOptions<Fruit>(
        option("--oranges").convert { Oranges(it) },
        option("--apples").int().convert { Apples(it) }
    )

    override fun run() = echo(fruit)
}
$ ./order --apples=10
Apples(count=10)
$ ./order --oranges=small
Oranges(size=small)
$ ./order --apples=10 --oranges=large
Oranges(size=large)

You can enforce that only one of the options is given with single:

val fruit: Fruit? by mutuallyExclusiveOptions<Fruit>(
        option("--apples").convert { Apples(it.toInt()) },
        option("--oranges").convert { Oranges(it) }
).single()
$ ./order --apples=10 --oranges=small
Usage: order [<options>]

Error: option --apples cannot be used with --oranges

Like regular options, you can make the entire group required, or give it a default value.

Like other option groups, you can specify a name and help text for the group if you want to set the group apart in the help output.

Co-Occurring Option Groups

Sometimes you have a set of options that only make sense when specified together. To enforce this, you can make an option group cooccurring.

Co-occurring groups must have at least one required option, and may also have non-required options. The required constraint is enforced if any of the options in the group are given on the command line. If none of the options are given, the value of the group is null.

class UserOptions : OptionGroup() {
    val name by option().required()
    val age by option().int()
}
class Tool : CliktCommand() {
    val userOptions by UserOptions().cooccurring()
    override fun run() {
        userOptions?.let {
            echo(it.name)
            echo(it.age)
        } ?: echo("No user options")
    }
}
$ ./tool
No user options
$ ./tool --name=jane --age=30
jane
30
$ ./tool --age=30
Usage: tool [<options>]

Error: Missing option "--name".

Like other option groups, you can specify a name and help text for the group if you want to set the group apart in the help output.

Choice and Switch Options With Groups

If you have different groups of options that only make sense when another option has a certain value, you can use groupChoice and groupSwitch.

groupChoice options are similar to choice options, but instead of mapping a value to a single new type, they map a value to a co-occurring OptionGroup. Options for groups other than the selected one are ignored, and only the selected group’s required constraints are enforced. In the same way, groupSwitch options are similar to switch options.

sealed class LoadConfig(name: String): OptionGroup(name)
class FromDisk : LoadConfig("Options for loading from disk") {
    val path by option().file().required()
    val followSymlinks by option().flag()
}

class FromNetwork: LoadConfig("Options for loading from network") {
    val url by option().required()
    val username by option().prompt()
    val password by option().prompt(hideInput = true)
}

class Tool : CliktCommand() {
    val load by option().groupChoice(
            "disk" to FromDisk(),
            "network" to FromNetwork()
    )

    override fun run() {
        when(val it = load) {
            is FromDisk -> echo("Loading from disk: ${it.path}")
            is FromNetwork -> echo("Loading from network: ${it.url}")
            null -> echo("Not loading")
        }
    }
}
$ ./tool --load=disk --path=./config --follow-symlinks
Loading from disk: .\config
$ ./tool --load=network --url=www.example.com --username=admin
Password: *******
Loading from network: www.example.com
$ ./tool --load=disk
Usage: cli [<options>]

Error: Missing option "--path".
$ ./tool --load=whoops
Usage: cli [<options>]

Error: Invalid value for "--load": invalid choice: whoops. (choose from disk, network)

Number Options Without a Name

If you have an int or long option, you might want to allow it to be specified without need the option name. For example, git log -2 and git log -n 2 are equivalent. You can add an option like this by passing acceptsValueWithoutName=true to int() or long().

class Tool : CliktCommand() {
    val level by option("-l", "--level", metavar = "<number>")
        .int(acceptsValueWithoutName = true)

    override fun run() {
        echo("Level: $level")
    }
}
$ ./tool -20
Level: 20
$ ./tool --level=3
Level: 3
$ ./tool --help
Usage: tool [<options>]

Options:
-<number>, -l, --level <number>
-h, --help             Show this message and exit

Caution

You may only have one option with acceptsValueWithoutName=true per command

Prompting For Input

In some cases, you might want to create an option that uses the value given on the command line if there is one, but prompt the user for input if one is not provided. Clikt can take care of this for you with the prompt function.

class Hello : CliktCommand() {
    val name by option().prompt()
    override fun run() {
        echo("Hello $name")
    }
}
./hello --name=foo
Hello foo
./hello
Name: foo
Hello foo

The default prompt string is based on the option name, but prompt takes a number of parameters to customize the output.

Password Prompts

You can also create an option that uses a hidden prompt and asks for confirmation. This combination of behavior is commonly used for passwords.

class Login : CliktCommand() {
    val password by option().prompt(requireConfirmation = true, hideInput = true)
    override fun run() {
        echo("Your hidden password: $password")
    }
}
$ ./login
Password:
Your hidden password: hunter2

Eager Options

Sometimes you want an option to halt execution immediately and print a message. For example, the built-in --help option, or the --version option that many programs have.

The --help option is added automatically to commands, and --version can be added using versionOption. Since these options don’t have values, you can’t define them using property delegates. Instead, call the function on a command directly, either in an init block, or on a command instance.

These definitions are equivalent:

class Cli : NoOpCliktCommand() {
    init {
        versionOption("1.0")
    }
}
fun main(args: Array<String>) = Cli().main(args)
class Cli : NoOpCliktCommand()
fun main(args: Array<String>) = Cli().versionOption("1.0").main(args)
$ ./cli --version
cli version 1.0

Custom Eager Options

If you want to define your own option with a similar behavior, you can do so by calling eagerOption. This function takes an action that is called when the option is encountered on the command line. To print a message and halt execution normally from the callback, you can throw a PrintMessage exception, and CliktCommand.main will take care of printing the message. If you want to exit normally without printing a message, you can throw Abort(error=false) instead.

You can define your own version option like this:

class Cli : NoOpCliktCommand() {
    init {
        eagerOption("--version") {
            throw PrintMessage("$name version 1.0")
        }
    }
}

Custom Eager Options With Values

If you need an eager option that takes a value, pass eager=true to option().

val color by option(eager=true).flag("--no-color", default=true)

Warning

Eager options can’t reference other options or arguments, since they’re evaluated before parsing the rest of the command line. They can be declared in regular OptionGroups, but not in other types of groups like switch groups.

Deprecating Options

You can communicate to users that an option is deprecated with option().deprecated(). By default, this function will add a tag to the option’s help message, and print a warning to stderr if the option is used.

You can customize or omit the warning message and help tags, or change the warning into an error.

class Cli : CliktCommand() {
   val opt by option(help = "option 1").deprecated()
   val opt2 by option(help = "option 2").deprecated("WARNING: --opt2 is deprecated, use --new-opt instead", tagName = null)
   val opt3 by option(help = "option 3").deprecated(tagName = "pending deprecation", tagValue = "use --new-opt instead")
   val opt4 by option(help = "option 4").deprecated(error = true)

   override fun run() = echo("command run")
}
$ ./cli --opt=x
WARNING: option --opt is deprecated
command run
$ ./cli --opt2=x
WARNING: --op2 is deprecated, use --new-opt instead
command run
$ ./cli --opt3=x
WARNING: option --opt3 is deprecated
command run
$ ./cli --opt4=x
ERROR: option --opt4 is deprecated
$ ./cli --help
Usage: cli [<options>]

Options:
  --opt <text>   option 1 (deprecated)
  --opt2 <text>  option 2
  --opt3 <text>  option 3 (pending deprecation: use --new-opt instead)
  --opt4 <text>  option 4 (deprecated)

Unknown Options

You may want to collect unknown options for manual processing. You can do this by overriding treatUnknownOptionsAsArgs = true in your command. This will cause Clikt to treat unknown options as positional arguments rather than reporting an error when one is encountered. You’ll need to define an argument().multiple() property to collect the options, otherwise an error will still be reported.

class Wrapper : CliktCommand() {
    init { context { allowInterspersedArgs = false }}
    override val treatUnknownOptionsAsArgs = true

    val command by option().required()
    val arguments by argument().multiple()

    override fun run() {
        val cmd = (listOf(command) + arguments).joinToString(" ")
        val proc = Runtime.getRuntime().exec(cmd)
        echo(proc.inputStream.bufferedReader().readText())
        proc.waitFor()
    }
}
$ ./wrapper --command=git tag --help | head -n4
GIT-TAG(1)                        Git Manual                        GIT-TAG(1)

NAME
       git-tag - Create, list, delete or verify a tag object signed with GPG

Warning

Multiple short options in a single token (e.g. using -abc to specify -a, -b, and -c in a single token) will still report an error if it contains a mixture of known and unknown options. To avoid this, don’t declare single-letter names for options in commands that use treatUnknownOptionsAsArgs.

You’ll often want to set allowInterspersedArgs = false on your Context when using treatUnknownOptionsAsArgs. You may also find that subcommands are a better fit than treatUnknownOptionsAsArgs for your use case.

Values From Environment Variables

Clikt supports reading option values from environment variables if they aren’t given on the command line. This feature is helpful when automating tools. For example, when using git commit, you can set the author date with a command line parameter: git commit --date=10/21/2015. But you can also set it with an environment variable: GIT_AUTHOR_DATE=10/21/2015 git commit.

Clikt will read option values from environment variables as long as it has an envvar name for the option. There are two ways to set that name: you can set the name manually for an option, or you can enable automatic envvar name inference.

To set the envvar name manually, pass the name to option:

class Hello : CliktCommand() {
    val name by option(envvar = "MY_NAME")
    override fun run() {
        echo("Hello $name")
    }
}
$ export MY_NAME=Foo
$ ./hello
Hello Foo
$ export MY_NAME=Foo
$ ./hello --name=Bar
Hello Bar

You can enable automatic envvar name inference by setting the autoEnvvarPrefix on a command’s context. This will cause all options without an explicit envvar name to be given an uppercase underscore-separated envvar name. Since the prefix is set on the context, it is propagated to subcommands. If you have a subcommand called foo with an option --bar, and your prefix is MY_TOOL, the option’s envvar name will be MY_TOOL_FOO_BAR.

class Hello : CliktCommand() {
    init {
        context { autoEnvvarPrefix = "HELLO" }
    }
    val name by option()
    override fun run() {
        echo("Hello $name")
    }
}
$ export HELLO_NAME=Foo
$ ./hello
Hello Foo

Multiple Values from Environment Variables

You might need to allow users to specify multiple values for an option in a single environment variable. You can do this by creating an option with split.

Flag Option Values from Environment Variables

For flag options, any of the following (case-insensitive) environment variable values will be interpreted as true:

  • "true", "t", "1", "yes", "y", "on"

The following (case-insensitive) values wil be interpreted as false:

  • "false", "f", "0", "no", "n", "off"

All other values are invalid.

Overriding system environment variables

You can set a custom function that will be used instead of the system environment variables with ContextBuilder.readEnvvar.

@Test
fun `test envvar`() {
    val envvars = mapOf("MY_TOOL_OPTION" to "value")
    val tool = MyTool().context {
        readEnvvar = { envvars[it] }
    }
    tool.parse(emptyList())
    assertEquals("value", tool.option)
}

Values from Configuration Files

Clikt also supports reading option values from one or more configuration files (or other sources) when they aren’t present on the command line. For example, when using git commit, you can set the author email with a command line parameter: git commit --author='Clikt <clikt@example.com>. But you can also set it in your git configuration file: user.email=clikt@example.com.

Clikt allows you to specify one or more sources of option values that will be read from with the Context.valueSource builder.

class Hello : CliktCommand() {
    init {
        context {
            valueSource = PropertiesValueSource.from("myconfig.properties")
        }
    }
    val name by option()
    override fun run() {
        echo("Hello $name")
    }
}
$ echo "name=Foo" > myconfig.properties
$ ./hello
Hello Foo

You can also pass multiple sources to Context.valueSources, and each source will be searched for the value in order.

Clikt includes support for reading values from a map, and (on JVM) from Java Properties files. For these two sources, you can customize the keys used to look up options by passing the result of ValueSource.getKey or ValueSource.envvarKey to the source’s getKey constructor parameter.

You can add any other file type by implementing ValueSource. See the JSON sample for an implementation that uses kotlinx.serialization to load values from JSON files.

Configuration Files and Environment Variables

Every option can read values from both environment variables and configuration files. By default, Clikt will use the value from an environment variable before the value from a configuration file, but you can change this by setting Context.readEnvvarBeforeValueSource to false.

Windows and Java-Style Option Prefixes

When specifying option names manually, you can use any prefix (as long as it’s entirely punctuation).

For example, you can make a Windows-style interface with slashes:

class Hello: CliktCommand() {
    val name by option("/name", help="your name")
    override fun run() {
        echo("Hello, $name!")
    }
}
$ ./hello /name Foo
Hello, Foo!

Or you can make a Java-style interface that uses single-dashes for long options:

class Hello: CliktCommand() {
    val name by option("-name", help="your name")
    override fun run() {
        echo("Hello, $name!")
    }
}
$ ./hello -name Foo
Hello, Foo!

Note

Inferred names will always have a POSIX-style prefix like --name. If you want to use a different prefix, you should specify all option names manually.

Option Transformation Order

Clikt has a large number of extension functions that can modify options. When applying multiple functions to the same option, there’s only one valid order for the functions to be applied. For example, option().default(3).int() will not compile, because default must be applied after the value type conversion.

You can call convert multiple times, but you can only apply one transform of each other type. So option().default("").multiple() is invalid, since default and multiple both transform the call list (if you need a custom default value for multiple, you can pass it one as an argument).

Here’s an integer option with one of each available transform in a valid order:

val opt: Pair<Int, Int> by option("-o", "--opt")
        .int()
        .restrictTo(1..100)
        .pair()
        .default(1 to 2)
        .validate { require(it.second % 2 == 0) }