Introduction
Typer is the "FastAPI of CLIs" — same author (Sebastián Ramírez), same type-hint-first philosophy. It wraps Click but lets you declare options and arguments just by writing a Python function signature. Your type hints become validation, and your docstrings become help text.
With over 15,000 GitHub stars, Typer is used by dbt Cloud CLI, typer itself, and thousands of tools that want modern Python CLIs without Click boilerplate.
What Typer Does
Typer reads your function signature: parameters without defaults become required arguments, parameters with defaults become options, bool params become flags, and types (int, float, Path, Enum) become validators. It generates --help, shell completion, and nested subcommands automatically.
Architecture Overview
@app.command()
def fn(a: int, b: str = "x", flag: bool = False): ...
|
[Typer Introspection]
Read signature + type hints
|
[Click Command Tree] (under the hood)
|
[Argv Parser]
Validation + type coercion
|
[Your function called]
|
[Rich output] (auto-wired for pretty errors)Self-Hosting & Configuration
import typer
from typing import Optional
from pathlib import Path
from enum import Enum
app = typer.Typer(help="Awesome CLI")
users_app = typer.Typer(help="Manage users")
app.add_typer(users_app, name="users")
class Format(str, Enum):
json = "json"
yaml = "yaml"
@app.command()
def export(
out: Path = typer.Argument(..., help="Output file"),
fmt: Format = Format.json,
verbose: bool = typer.Option(False, "--verbose", "-v"),
):
"""Export data to OUT in the chosen format."""
if verbose:
typer.secho(f"Exporting to {out} as {fmt.value}", fg="green")
out.write_text("...")
@users_app.command("add")
def users_add(name: str, admin: bool = False):
typer.echo(f"Added {name} (admin={admin})")
if __name__ == "__main__":
app()Key Features
- Type hints become parsers — int, float, Path, bool, Enum, Optional
- Subcommands — nest Typer apps for git-style command trees
- Auto-generated help — from docstrings and parameter metadata
- Shell completions —
--install-completionfor bash/zsh/fish/PowerShell - Prompting —
typer.prompt()andtyper.confirm()for interactive input - Rich integration — colorful tracebacks and prompts
- Testable —
CliRunnerfrom Click works unchanged - FastAPI DX — same ergonomics for developers who love FastAPI
Comparison with Similar Tools
| Feature | Typer | Click | argparse | Fire | docopt |
|---|---|---|---|---|---|
| Type hints drive parsing | Yes | No (decorators) | No | Partial | No (docstring) |
| Subcommands | Yes | Yes | Nested | Yes | Limited |
| Auto completion | Yes | Yes | No | No | No |
| Learning Curve | Very Low | Low | Moderate | Very Low | Moderate |
| Built on | Click | Standalone | stdlib | Inspect | Docstring DSL |
| Best For | Modern Python | Any Python CLI | Stdlib only | Quick scripts | Declarative |
FAQ
Q: Typer vs Click — do I need both? A: Typer is a layer on top of Click. Use Typer for new projects; drop down to Click features when you need advanced custom types or parsers.
Q: How do I test a Typer app?
A: from typer.testing import CliRunner. Invoke with args and check result.exit_code and result.stdout.
Q: Does Typer support async commands?
A: Not directly — Typer invokes sync functions. Use asyncio.run(...) inside your command, or wrap with a small helper.
Q: Can I use Typer without global app state?
A: Yes. You can use @typer.run on a single function for one-off scripts, no Typer() instance needed.
Sources
- GitHub: https://github.com/fastapi/typer
- Docs: https://typer.tiangolo.com
- Author: Sebastián Ramírez (tiangolo)
- License: MIT