Skip to main content

Command Management

Command management in Typer is centered around the Typer class, which acts as a registry for commands and subcommand groups. While Typer is built on top of Click, it uses Python type hints and decorators to define CLI structures, automatically handling the conversion of function signatures into CLI parameters.

Single Command Scripts

For simple scripts that only require a single command, Typer provides the typer.run() function. This is the quickest way to create a CLI without explicitly instantiating a Typer app.

import typer

def main(name: str):
print(f"Hello {name}")

if __name__ == "__main__":
typer.run(main)

Internally, typer.run() creates an implicit Typer instance, registers the function as a command, and executes it.

The Typer Class and Command Registration

For applications with multiple commands, you use the Typer class found in typer/main.py. Commands are registered using the @app.command() decorator.

Command Naming

By default, Typer converts function names to kebab-case for the CLI. For example, a function named create_user becomes the command create-user. This logic is implemented in typer.main.get_command_name:

def get_command_name(name: str) -> str:
return name.lower().replace("_", "-")

You can override this by providing a name argument to the decorator: @app.command("create").

Command Metadata

The @app.command() decorator supports various metadata options that influence the generated help text and CLI behavior:

  • help: The description of the command. If omitted, Typer uses the function's docstring.
  • hidden: If set to True, the command will not appear in the help output.
  • deprecated: Marks the command as deprecated in the help text.
  • rich_help_panel: Organizes commands into specific panels when using Rich-formatted help.

Organizing Subcommands and Groups

Typer allows nesting applications to create complex CLI hierarchies using app.add_typer(). This method effectively turns another Typer instance into a subcommand group.

Nesting Applications

In larger projects, it is common to define commands in separate modules and then aggregate them in a main entry point.

users.py

import typer

app = typer.Typer()

@app.command()
def create(user_name: str):
print(f"Creating user: {user_name}")

main.py

import typer
import users

app = typer.Typer()
app.add_typer(users.app, name="users")

The "No Name" Gotcha

If you call add_typer() without a name argument, the commands from the sub-app are added directly to the parent app instead of being nested under a group. However, as noted in typer/main.py, if the sub-app has a callback defined, Typer will issue a warning because callbacks are not supported for "nameless" sub-apps:

if not sub_group.name:
if sub_group.callback:
import warnings
warnings.warn(
"The 'callback' parameter is not supported by Typer when using `add_typer` without a name",
stacklevel=5,
)

Shared Logic with Callbacks

The @app.callback() decorator is used to define logic that runs before any subcommand. This is the primary way to implement global options (like --verbose or --config) that apply to all commands in a group.

import typer

app = typer.Typer()
state = {"verbose": False}

@app.callback()
def main(verbose: bool = False):
"""
Manage users in the awesome CLI app.
"""
if verbose:
print("Will write verbose output")
state["verbose"] = True

If a Typer instance has only one command and no callback, it behaves as a single command. If it has multiple commands or a callback, it is treated as a TyperGroup (a subclass of click.Group).

Custom Command and Group Classes

Typer uses custom subclasses of Click's core classes to integrate its features, particularly Rich formatting and enhanced exception handling. These are defined in typer/core.py:

  • TyperCommand: Inherits from click.Command. It overrides format_options and main to support Rich-based help and error reporting.
  • TyperGroup: Inherits from click.Group. It manages the collection of subcommands and ensures they are also instantiated as TyperCommand or TyperGroup objects.

You can provide your own subclasses to the Typer constructor or the @app.command() decorator via the cls parameter if you need to extend Click's behavior directly.

Rich Integration and Formatting

Typer's command management is tightly integrated with the Rich library for "pretty" output. This is controlled by the TYPER_USE_RICH environment variable and the rich_markup_mode parameter.

When HAS_RICH is true (the default if the library is installed), Typer uses typer.rich_utils to format help screens, including:

  • Markdown support: If rich_markup_mode="markdown", docstrings are rendered as Markdown.
  • Colorized Panels: Commands and options can be grouped into visually distinct panels using rich_help_panel.
  • Pretty Exceptions: If a command fails, Typer uses Rich to print a formatted traceback unless pretty_exceptions_enable is set to False.