Python click
last modified October 30, 2026 Python 3.15
In this article we show how to create command line interfaces with the
click module.
Python click
The click module is used to create command line (CLI)
applications. It is an easy-to-use alternative to the standard
argparse module. It allows arbitrary nesting of commands,
generates help pages automatically, validates and converts input values, and
supports prompts, environment variables, and colours. It was created for the
Flask web framework.
Commands are the basic building blocks of an application. They are defined
with the click.command decorator. Values are passed to commands
via options and arguments. Options are added with the
click.option decorator and arguments with
click.argument. Options are named and usually optional;
arguments are positional.
The module is installed with the following command. The examples use click 8.5.
$ pip install click
Simple example
The following is a trivial CLI example.
import click
@click.command()
def hello():
click.echo('Hello there')
if __name__ == '__main__':
hello()
The example creates a command that outputs a message.
@click.command()
def hello():
click.echo('Hello there')
The decorator turns the function into a command. Click uses
echo instead of print; it handles bytes and
strings consistently and removes colour codes when the output is not a
terminal.
Click adds a --help option automatically and rejects unknown
options with an error message and exit code 2.
$ python simple.py Hello there $ python simple.py --help Usage: simple.py [OPTIONS] Options: --help Show this message and exit. $ python simple.py --verbose Usage: simple.py [OPTIONS] Try 'simple.py --help' for help. Error: No such option '--verbose'.
Arguments
Arguments are added with the click.argument decorator. They are
passed to the function as parameters of the same name and may have default
values.
import click
@click.command()
@click.argument('name', default='guest')
def hello(name):
click.echo(f'Hello {name}')
if __name__ == '__main__':
hello()
The example builds a message with the given argument value. If there is no argument, the default guest is used.
$ python argument.py Peter Hello Peter $ python argument.py Hello guest
Argument types
The type parameter sets the type of a value. Click converts the
value and reports an error if the conversion fails. The default type is
str.
import click
@click.command()
@click.argument('name')
@click.argument('age', type=int)
def hello(name, age):
click.echo(f'{name} is {age} years old')
if __name__ == '__main__':
hello()
In the example, we have two arguments: name and age. They are taken positionally.
@click.argument('age', type=int)
The age argument is converted to an integer. Arguments without a
default are required.
$ python argument_type.py Peter 34 Peter is 34 years old $ python argument_type.py Peter old Usage: argument_type.py [OPTIONS] NAME AGE Try 'argument_type.py --help' for help. Error: Invalid value for 'AGE': 'old' is not a valid integer. $ python argument_type.py Peter Usage: argument_type.py [OPTIONS] NAME AGE Try 'argument_type.py --help' for help. Error: Missing argument 'AGE'.
Variable number of arguments
With nargs=-1, an argument takes any number of values, which are
passed as a tuple.
import click
from math import prod
@click.command()
@click.argument('vals', type=int, nargs=-1, required=True)
def process(vals):
click.echo(f'The values are {vals}')
click.echo(f'The sum is {sum(vals)}')
click.echo(f'The product is {prod(vals)}')
if __name__ == '__main__':
process()
The process command calculates the sum and the product of the
given integers.
@click.argument('vals', type=int, nargs=-1, required=True)
Each value is converted to an integer. An argument with
nargs=-1 accepts zero values by default; with
required=True at least one value must be given.
$ python variable_args.py 1 2 3 4 5 The values are (1, 2, 3, 4, 5) The sum is 15 The product is 120 $ python variable_args.py Usage: variable_args.py [OPTIONS] VALS... Try 'variable_args.py --help' for help. Error: Missing argument 'VALS...'.
Options
Options are added with the click.option decorator. They usually
have a short name with one dash and a long name with two dashes.
import click
@click.command()
@click.option('-n', '--count', type=int, default=1, show_default=True,
help='Number of dots to print.')
@click.option('-c', '--char', default='.', help='Character to use.')
def dots(count, char):
"""Print a line of dots."""
click.echo(char * count)
if __name__ == '__main__':
dots()
The command prints a line of characters.
@click.option('-n', '--count', type=int, default=1, show_default=True,
help='Number of dots to print.')
The help text appears on the help page, and
show_default adds the default value to it.
def dots(count, char):
"""Print a line of dots."""
The docstring of the function becomes the description of the command.
The value can follow the option name as a separate word, directly after a
short name (-n5), or after an equals sign
(--char=#).
$ python dots.py . $ python dots.py --count 17 ................. $ python dots.py -n 5 -c '*' ***** $ python dots.py -n5 --char=# ##### $ python dots.py --help Usage: dots.py [OPTIONS] Print a line of dots. Options: -n, --count INTEGER Number of dots to print. [default: 1] -c, --char TEXT Character to use. --help Show this message and exit.
Option names
The name of the function parameter is derived from the longest option name; dashes are replaced with underscores. An explicit name can be given as an argument without dashes.
import click
@click.command()
@click.option('-s', '--string')
@click.option('--max-size', type=int)
@click.option('-o', 'output_file')
def show(string, max_size, output_file):
click.echo(f'{string=} {max_size=} {output_file=}')
if __name__ == '__main__':
show()
We define three options.
@click.option('--max-size', type=int)
@click.option('-o', 'output_file')
The --max-size option is passed as max_size. The
-o option is passed as output_file. Options that are
not given are None.
$ python option_names.py -s sky --max-size 10 -o out.txt string='sky' max_size=10 output_file='out.txt' $ python option_names.py --string cloud string='cloud' max_size=None output_file=None
Required options and tuples
An option can be made mandatory with required=True. A tuple type
makes an option take several values.
import click
@click.command()
@click.option('--name', required=True)
@click.option('--data', type=(str, int))
def output(name, data):
click.echo(f'name={name} data={data}')
if __name__ == '__main__':
output()
The --name option is required, and the --data
option takes a string and an integer.
@click.option('--data', type=(str, int))
The values become a Python tuple with converted types.
$ python required.py --name Peter --data Lucy 23
name=Peter data=('Lucy', 23)
$ python required.py --data Lucy 23
Usage: required.py [OPTIONS]
Try 'required.py --help' for help.
Error: Missing option '--name'.
Choices and ranges
The click.Choice type restricts a value to a set of options,
and IntRange and FloatRange restrict numbers to a
range.
import click
@click.command()
@click.option('--format', 'fmt', type=click.Choice(['json', 'csv', 'xml'],
case_sensitive=False), default='json', show_default=True)
@click.option('--level', type=click.IntRange(1, 9), default=6)
@click.option('--ratio', type=click.FloatRange(0, 1, clamp=True), default=0.5)
def export(fmt, level, ratio):
click.echo(f'format={fmt} level={level} ratio={ratio}')
if __name__ == '__main__':
export()
We define an export command with three validated options.
type=click.Choice(['json', 'csv', 'xml'], case_sensitive=False)
The choice is case-insensitive, and the value is normalized to the listed
form; CSV becomes csv.
@click.option('--ratio', type=click.FloatRange(0, 1, clamp=True), default=0.5)
With clamp=True, values outside the range are clamped to the
nearest boundary instead of being rejected.
$ python choices.py --format CSV --level 9 --ratio 1.7 format=csv level=9 ratio=1 $ python choices.py --format yaml Usage: choices.py [OPTIONS] Try 'choices.py --help' for help. Error: Invalid value for '--format': 'yaml' is not one of 'json', 'csv', 'xml'. $ python choices.py --level 12 Usage: choices.py [OPTIONS] Try 'choices.py --help' for help. Error: Invalid value for '--level': 12 is not in the range 1<=x<=9.
Flags
Flags are boolean options. They are defined as a pair of names separated by a
slash, or with is_flag=True. A counting option records how many
times it was given.
import click
@click.command()
@click.argument('word')
@click.option('--shout/--no-shout', default=False, help='Print in uppercase.')
@click.option('--reverse', is_flag=True, help='Reverse the word.')
@click.option('-v', '--verbose', count=True, help='Increase verbosity.')
def output(word, shout, reverse, verbose):
if reverse:
word = word[::-1]
if shout:
word = word.upper()
if verbose:
click.echo(f'verbosity level {verbose}')
click.echo(word)
if __name__ == '__main__':
output()
We define three kinds of flags.
@click.option('--shout/--no-shout', default=False, help='Print in uppercase.')
The --shout flag sets the value to True and
--no-shout to False. The last one given wins.
@click.option('--reverse', is_flag=True, help='Reverse the word.')
@click.option('-v', '--verbose', count=True, help='Increase verbosity.')
The --reverse flag is True when present. With
count=True, -vvv gives the value 3.
$ python flags.py sky sky $ python flags.py --shout sky SKY $ python flags.py --shout --no-shout sky sky $ python flags.py --reverse --shout -vvv sky verbosity level 3 YKS
Multiple options
With multiple=True, an option can be given several times. The
values are collected in a tuple.
import click
@click.command()
@click.option('--word', '-w', multiple=True)
def words(word):
click.echo(word)
click.echo(', '.join(word))
if __name__ == '__main__':
words()
We collect words from the --word/-w options. Without
the option, the tuple is empty.
$ python multiples.py -w sky --word forest --word rock -w cloud
('sky', 'forest', 'rock', 'cloud')
sky, forest, rock, cloud
$ python multiples.py
()
Prompts
With the prompt parameter, click asks the user for a value that
was not given on the command line.
import click
@click.command()
@click.option('--name', prompt='Your name', help='Provide your name.')
@click.option('--age', prompt=True, type=int)
def hello(name, age):
click.echo(f'Hello {name}, you are {age}')
if __name__ == '__main__':
hello()
The command asks for a name and an age.
@click.option('--name', prompt='Your name', help='Provide your name.')
@click.option('--age', prompt=True, type=int)
The prompt text is given explicitly or, with prompt=True,
derived from the option name. When the options are given on the command
line, there is no prompt.
In the second run, we feed the answers through a pipe. Piped input is not echoed, so the answers do not appear in the output. The invalid age is rejected and the prompt is repeated.
$ python prompt.py --name Peter --age 34 Hello Peter, you are 34 $ printf 'Lucy\nabc\n23\n' | python prompt.py Your name: Age: Error: 'abc' is not a valid integer. Age: Hello Lucy, you are 23
Environment variables
Option values can be read from environment variables with the
envvar parameter. A value on the command line takes
precedence.
import click
@click.command()
@click.option('--user', envvar='APP_USER', show_envvar=True)
@click.option('--token', envvar='APP_TOKEN', required=True)
def login(user, token):
click.echo(f'user={user} token={token[:4]}...')
if __name__ == '__main__':
login()
The command takes a user name and a token, which should not be typed on the command line where it would end up in the shell history.
@click.option('--user', envvar='APP_USER', show_envvar=True)
The show_envvar parameter adds the variable name to the help
page.
$ APP_USER=jdoe APP_TOKEN=s3cr3t-value python env_var.py user=jdoe token=s3cr... $ APP_TOKEN=s3cr3t-value python env_var.py --user admin user=admin token=s3cr... $ python env_var.py --help Usage: env_var.py [OPTIONS] Options: --user TEXT [env var: APP_USER] --token TEXT [required] --help Show this message and exit.
The click.File type
The click.File type opens a file for reading or writing. The
file is closed automatically when the command finishes. The special name
- stands for standard input or output.
sky cloud water forest rock moon falcon lake
We work with this text file.
import click
@click.command()
@click.argument('file', type=click.File('r'))
@click.option('-n', '--lines', default=10, show_default=True)
def head(file, lines):
for i, line in enumerate(file):
if i == lines:
break
click.echo(line, nl=False)
if __name__ == '__main__':
head()
We create an equivalent of the Linux head command.
@click.argument('file', type=click.File('r'))
The function receives an open file object. A missing file is reported as an error before the function is called.
$ python head.py words.txt -n 4 sky cloud water forest $ printf 'one\ntwo\nthree\n' | python head.py - -n 2 one two $ python head.py missing.txt Usage: head.py [OPTIONS] FILE Try 'head.py --help' for help. Error: Invalid value for 'FILE': 'missing.txt': No such file or directory
The click.Path type
The click.Path type validates a path but does not open the file.
It is used when the program needs the name rather than the content, or opens
the file itself.
import pathlib
import click
@click.command()
@click.argument('src', type=click.Path(exists=True, dir_okay=False,
path_type=pathlib.Path))
def info(src):
click.echo(f'{src.name}: {src.stat().st_size} bytes')
click.echo(f'{len(src.read_text().splitlines())} lines')
if __name__ == '__main__':
info()
The command prints the size and the number of lines of a file.
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path)
The path must exist and must not be a directory. With
path_type, the value is converted to a Path
object.
$ python path_type.py words.txt words.txt: 45 bytes 8 lines $ python path_type.py . Usage: path_type.py [OPTIONS] SRC Try 'path_type.py --help' for help. Error: Invalid value for 'SRC': File '.' is a directory.
Coloured output
The style function wraps text in ANSI escape codes for colours
and styles; secho combines style and
echo.
import click
text = click.style('Hello there', fg='blue', bold=True)
print(repr(text))
print(repr(click.unstyle(text)))
click.echo(text)
click.secho('Error!', fg='red', err=True)
We style a message and print it.
print(repr(text)) print(repr(click.unstyle(text)))
The repr shows the escape codes for blue, bold, and reset. The
unstyle function removes them.
click.echo(text)
click.secho('Error!', fg='red', err=True)
When the output is not a terminal, as here, echo strips the
codes automatically, so redirected output stays clean. The
err=True option writes to standard error, which is why the
second run without stderr does not show it.
$ python styling.py '\x1b[34m\x1b[1mHello there\x1b[0m' 'Hello there' Hello there Error! $ python styling.py 2>/dev/null '\x1b[34m\x1b[1mHello there\x1b[0m' 'Hello there' Hello there
Command groups
Commands can be organized in groups, created with the
click.group decorator. Each subcommand has its own options and
help.
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
"""Manage a list of tasks."""
ctx.obj = {'debug': debug, 'tasks': ['buy milk', 'write report']}
@cli.command(name='list')
@click.pass_obj
def list_tasks(obj):
"""List all tasks."""
for i, task in enumerate(obj['tasks'], 1):
click.echo(f'{i}. {task}')
@cli.command()
@click.argument('title')
@click.pass_obj
def add(obj, title):
"""Add a new task."""
obj['tasks'].append(title)
if obj['debug']:
click.echo(f'[debug] tasks: {obj["tasks"]}')
click.echo(f'Added: {title}')
if __name__ == '__main__':
cli()
We create a small task manager with list and add
commands.
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
ctx.obj = {'debug': debug, 'tasks': ['buy milk', 'write report']}
The group function runs before any subcommand. It stores shared state in the
context object ctx.obj.
@cli.command(name='list') @click.pass_obj def list_tasks(obj):
The name parameter sets the command name, so the function does
not have to shadow the built-in list. The
pass_obj decorator passes ctx.obj to the
function.
For a mistyped command, click suggests the closest match.
$ python groups.py --help Usage: groups.py [OPTIONS] COMMAND [ARGS]... Manage a list of tasks. Options: --debug / --no-debug --help Show this message and exit. Commands: add Add a new task. list List all tasks. $ python groups.py list 1. buy milk 2. write report $ python groups.py --debug add 'call John' [debug] tasks: ['buy milk', 'write report', 'call John'] Added: call John $ python groups.py lst Usage: groups.py [OPTIONS] COMMAND [ARGS]... Try 'groups.py --help' for help. Error: No such command 'lst'. Did you mean 'list'?
Confirmation
Destructive commands should ask for confirmation. The
confirmation_option decorator adds a prompt and a
--yes option to skip it.
import click
@click.command()
@click.argument('name')
@click.confirmation_option(prompt='Are you sure you want to drop the table?')
def drop(name):
click.echo(f'Dropped table {name}')
if __name__ == '__main__':
drop()
The command asks before dropping a table. Any answer other than yes aborts the command with exit code 1.
$ echo y | python confirm.py users Are you sure you want to drop the table? [y/N]: Dropped table users $ echo n | python confirm.py users Are you sure you want to drop the table? [y/N]: Aborted! $ python confirm.py users --yes Dropped table users
Testing commands
The CliRunner class invokes commands in-process and captures
their output and exit code, which makes CLI code easy to test.
from click.testing import CliRunner from flags import output runner = CliRunner() result = runner.invoke(output, ['--shout', 'sky']) print(result.exit_code, repr(result.output)) result = runner.invoke(output, []) print(result.exit_code) print(result.output)
We test the output command from the flags example.
result = runner.invoke(output, ['--shout', 'sky']) print(result.exit_code, repr(result.output))
The arguments are passed as a list, as they would appear on the command line. A successful run has exit code 0.
result = runner.invoke(output, [])
The missing argument gives exit code 2 and the usual error message. In a test
suite, these checks become assert statements.
$ python test_cli.py 0 'SKY\n' 2 Usage: output [OPTIONS] WORD Try 'output --help' for help. Error: Missing argument 'WORD'.
Source
In this article we have created command line interfaces with the Python
click module.
Author
List all Python tutorials.