Skip to main content

docs/guides/options.md

# Options

Flags accepted by a command. Long form is derived from the atom name
(`:base_port` becomes `--base-port`); short aliases and every other behavior
are opt-in.

## Types

```elixir
option :port,    type: :integer
option :ratio,   type: :float
option :name,    type: :string     # default
option :verbose, type: :boolean    # also supports --no-verbose
option :v,       type: :count      # -vv -> 2
```

Typed values are coerced before your handler sees them. Invalid values
produce an error and never reach `run/2`.

## Short aliases

```elixir
option :port, type: :integer, short: :p
```

Accepts `-p 4000` or `--port 4000`.

## Defaults and env var fallback

```elixir
option :port, type: :integer, default: 4000, env: "PORT"
```

Priority: CLI flag > `PORT` env var > default.

## Choices

```elixir
option :format, type: :string, choices: ["json", "csv", "table"]
```

Rejected values produce an error before `run/2` runs.

## Repeated flags (`:multi`)

```elixir
option :tag, type: :string, multi: true
# --tag a --tag b  ->  args[:tag] == ["a", "b"]
```

## Multi-value flags (`:num_args`)

`:num_args` collects several values from a single flag invocation into a list.
Pass an integer for an exact count or a range for a variable count:

```elixir
option :point, type: :integer, num_args: 2
# --point 1 2   ->  args[:point] == [1, 2]

option :tags, type: :string, num_args: 1..3
# --tags a b c  ->  args[:tags] == ["a", "b", "c"]
```

Both the space-separated form (`--point 1 2`) and the `--flag=value` form work.
Collection stops at the next flag (a token starting with `-`) or at `--`, and
each value is coerced to the option's `:type`. A count outside the declared
range is a usage error:

```
--point 1   ->   error: --point expects 2 value(s), got 1
```

This is distinct from `:multi`, which repeats the whole flag (`--tag a --tag b`)
rather than consuming multiple tokens after one flag.

## Hyphen-leading values (`:allow_hyphen_values`)

A value that starts with `-` normally looks like a flag, so it is rejected.
Negative numbers are always accepted (`--range -5 5`). For other hyphen-leading
values, set `:allow_hyphen_values`:

```elixir
option :pattern, type: :string, allow_hyphen_values: true
# --pattern -v   ->  args[:pattern] == "-v"

option :range, type: :integer, num_args: 2, allow_hyphen_values: true
# --range -a -b  ->  args[:range] == ["-a", "-b"]
```

A single-value option consumes exactly the next token as its value, so a
following flag is still parsed normally (`--pattern x --verbose`). With
`:num_args`, collection consumes hyphen-leading tokens up to the declared count.

## Delimited values (`:value_delimiter`)

`:value_delimiter` splits one value on a string into a list:

```elixir
option :tags, type: :string, value_delimiter: ","
# --tags a,b,c  ->  args[:tags] == ["a", "b", "c"]

option :ids, type: :integer, value_delimiter: ","
# --ids 1,2,3   ->  args[:ids] == [1, 2, 3]
```

Each element is coerced to the option's `:type` and validated against
`:choices`. A string `:default` is split the same way, and it combines with
`:multi` (each occurrence is split and the results flattened). This differs
from `:num_args`, which reads several separate tokens after one flag.

## Optional value (`:default_missing_value`)

`:default_missing_value` is the value a flag takes when it is present with no
value, distinct from `:default` (the flag absent):

```elixir
option :color, type: :string, default: "auto", default_missing_value: "always"
# (absent)      ->  args[:color] == "auto"
# --color       ->  args[:color] == "always"
# --color=never ->  args[:color] == "never"
```

An explicit value must use the `--flag=value` form. `--color never` leaves
`never` as a positional argument, so `--color` still resolves to the missing
value.

## Custom value parsers (`:parse`)

`:parse` transforms a value into a domain type (an atom, a `Date`, a struct)
after `:type` coercion. See [Validation](validation.md) for the full contract:

```elixir
option :mode, type: :string, parse: fn
  "r" -> {:ok, :read}
  "w" -> {:ok, :write}
  _ -> {:error, "must be r or w"}
end
# --mode r  ->  args[:mode] == :read
```

## Aliases (long-form)

```elixir
option :color, type: :string, aliases: [:colour]
# --color red and --colour red both set args[:color]
```

## Global options

Propagate an option to every subcommand:

```elixir
option :verbose, type: :boolean, global: true
```

Children inherit the option spec but can override by redeclaring.

## Hiding options

```elixir
option :internal, type: :boolean, hide: true
```

Still accepted by the parser, not shown in help.

## Boolean negation

Every boolean option automatically accepts its `--no-<name>` inverse:

```elixir
option :color, type: :boolean, default: true

# --color        -> true
# --no-color     -> false
```

## Putting it together

```elixir
option :port, type: :integer, short: :p,
  default: 4000, env: "PORT",
  validate: fn p -> if p in 1024..65_535, do: :ok, else: {:error, "invalid port"} end,
  help: "Port to listen on"
```

## See also

- [Validation](validation.md) for `:validate` and cross-param validators.
- [Constraints](constraints.md) for `:conflicts_with`, `:requires`,
  `:required_if`, `:required_unless`, and groups.
- [Help and output](help_and_output.md) for `:help_heading`, `:display_order`,
  and other presentation controls.