# Typer: building Python CLIs from type hints

> Typer turns annotated Python functions into command line applications, and ships a typer command that can run plain scripts as CLIs. It is a thin, opinionated layer over Click, and the trade-offs follow from that.

**fastapi/typer** — Typer, build great CLIs. Easy to code. Based on Python type hints.

- Repository: https://github.com/fastapi/typer
- Website: https://typer.tiangolo.com/
- Stars: 20,040 · Forks: 984
- Language: Python
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/fastapi-typer

## The problem Typer solves for Python CLI authors

Writing a command line interface in Python usually means choosing between argparse, which is in the standard library but verbose, and Click, which is pleasant but asks you to declare parameters through decorators and explicit option names. Typer's pitch is that the function signature already contains most of that information. A parameter named name with the annotation str becomes a required positional argument; a parameter annotated bool with a default becomes a flag.

The README states the design goal directly: the simplest example adds only 2 lines of code to your app, 1 import and 1 function call. That is the audience. People who write small internal tools, data scripts and developer utilities in Python and do not want a separate argument-parsing layer. It is also aimed at teams that already use FastAPI, since the project describes itself as FastAPI's little sibling and the FastAPI of CLIs. The mental model transfers: annotate, and the framework derives the interface.

What it is not is a general-purpose shell framework or a configuration system. It parses arguments, renders help and wires completion. Everything else is your code.

## How Typer maps annotations onto a Click command tree

Typer is not a parser. The pyproject.toml lists shellingham, rich, annotated-doc and, on Windows, colorama as dependencies, but the command layer is Click's; the project's own description and the surrounding ecosystem treat Click as the engine underneath. Typer reads the type hints and defaults of your functions, builds Click commands from them, and hands rendering of help and errors to rich, which is why the README's sample output shows boxed panels with rounded corners rather than plain text.

There are two entry points. typer.run(main) takes a single function and implicitly creates an application around it. typer.Typer() creates an explicit app object, and each function decorated with @app.command() becomes a subcommand of a command group. The README's upgrade example moves from the first form to the second and notes that typer.run actually creates a Typer app implicitly, so the two are the same machinery at different levels of control.

Nesting is where the tree model shows. Because an app is an object, you can attach sub-apps and build arbitrarily deep command groups, which is the point of the README's grow large claim. The cost is that the shape of your CLI is now the shape of your Python module structure, and refactoring one means refactoring the other.

The second, less obvious product is the typer command itself, registered in pyproject.toml as typer = "typer.cli:main". It runs a script and converts it into a CLI even when that script never imports Typer. That is why the absolute minimum example is a plain function with a print call and no import at all.

## Installing Typer and running your first command

The README's installation path goes through uv. Install uv first, then add Typer to the project. This installs both the library and the typer command into the project's virtual environment.

```bash
uv add typer
```

The README notes that if you prefer pip, you should install typer inside a virtual environment and points to the installation guide at typer.tiangolo.com/tutorial/install for the alternative steps. It also notes that to use the typer command with shell completion you should activate the project environment and install completion, which the install guide covers.

Start with a file called main.py that does not import Typer at all:

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

Run it through the typer command. With no arguments you should see a Usage line followed by a boxed error reading Missing argument 'name'.

```bash
typer main.py run
```

Adding --help gives you a generated help screen with an Arguments panel listing name as a required str and an Options panel containing --help. Passing a value prints the greeting.

```bash
typer main.py run Camila
```

To use Typer from your own code, add the import and the call, then run the file with Python. The README shows uv run python main.py for this. The generated help is the same apart from the program name in the Usage line.

```python
import typer


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


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

From there, the upgrade is to declare an app and decorate two functions with @app.command(), then call app() under the __main__ guard instead of typer.run.

## Where Typer is the wrong tool

The PyPI classifier in pyproject.toml reads Development Status :: 4 - Beta. That is the project's own label, not an outside judgement, and it matters for anyone deciding whether to build a long-lived internal CLI on top of it. The 0.27.x releases land frequently, and a pre-1.0 version number means minor releases can carry behaviour changes. If your CLI is shipped to customers who expect argument parsing to keep working across upgrades, pin the version and read the release notes before bumping.

The Python floor is 3.10. pyproject.toml sets requires-python = ">=3.10" and classifies 3.10 through 3.14. Any environment still on 3.8 or 3.9 is out, and that includes a fair number of long-lived enterprise images.

There is also a scope mismatch. Typer's value comes from deriving an interface from annotations. If your script takes one optional flag, argparse in the standard library does the job with no dependency at all, and Typer's dependency set (shellingham, rich, annotated-doc, colorama on Windows) is weight you are carrying for nothing. Similarly, if your CLI must run on a Python installation where you cannot add packages, the standard library wins by default.

Finally, Typer does not hide Click. Anything Click does that Typer has not re-exported ends up in your code as a Click import, and at that point you are maintaining two mental models. The README does not document a migration or rollback path off Typer, so treat the wrapper as part of your dependency surface.

## Typer vs Click vs argparse: the actual difference

Click is the closest comparison because Typer sits on top of it. In Click you declare the interface explicitly: a function decorated with @click.command(), parameters declared with @click.option and @click.argument, names and types written out. You see the full command definition in one place, and help strings are attached to the decorators. This is more typing, and it is also more legible when a command has many parameters with non-obvious names.

Typer inverts that. The function signature is the declaration, and the README's shortest example is one import and one function call. The trade-off is that changing the interface means changing the signature, and the mapping from annotation to CLI behaviour is a convention you have to learn rather than read off the page.

argparse is a different axis. It is in the standard library, so it adds no dependency, and it is stable in the way a stdlib module is stable. It is also procedural: you build a parser object, add arguments one at a time, and dispatch. Nothing about it is derived from type hints. For a script with two flags, that is fine. For a tool with nested subcommands, groups and completion, you write a lot of boilerplate that Typer generates.

The practical split: argparse when the CLI is incidental, Click when you want the command definition visible and stable, Typer when the function signature is already the source of truth and you want completion and rich-formatted help without writing them.

## Maintenance, upgrades and the MIT licence

The repository is not archived, and the last push was on 2026-09-19. Releases are frequent: 0.27.0 on 2026-07-15, 0.27.1 on 2026-08-03 and 0.27.2 on 2026-08-28. That cadence is the upgrade cost. There is no 1.0, and the Beta classifier in pyproject.toml has not been removed, so treat each minor bump as something to check rather than assume.

Because Typer wraps Click, an upgrade can pull in Click and rich changes that surface as different help formatting or error output rather than as a Typer API change. Your tests should assert on exit codes and on the presence of expected strings in output, not on exact box drawing, or every rich release becomes a test failure.

The licence is MIT, declared in pyproject.toml as license = "MIT" with license-files = ["LICENSE"]. MIT is permissive: it allows commercial and closed-source use, and the obligation is to keep the copyright notice and licence text with copies or substantial portions of the software. That is the general shape of the licence, not legal advice for your situation; if you redistribute Typer inside a product, have your own counsel read the LICENSE file in the repository.

## Conclusion

Adopt Typer if your team already writes typed Python and you want argument parsing, help text and shell completion without hand-writing a parser; the two-line minimum example is real. Skip it if you need a stable, non-Beta API surface, if you cannot move to Python 3.10 or newer, or if your CLI is a single script with one flag. Before committing, check the release notes at typer.tiangolo.com/release-notes for the 0.27.x changes and read the Click documentation for anything Typer does not re-export, because the underlying behaviour is Click's.

## FAQ

### How do I install Typer in Python?

The README recommends installing uv first and then running uv add typer, which installs both the library and the typer command into the project's virtual environment. If you prefer pip, the README says to install typer inside a virtual environment and points to the installation guide for the alternative steps.

### How do I use Typer to build a CLI?

Define a function whose parameters carry type hints, then either call typer.run(main) under a __main__ guard or create a typer.Typer() app and decorate functions with @app.command() to get subcommands. Typer derives the arguments, the help text and the error messages from the signature.

### What is the difference between Typer and Click in Python?

Typer builds on Click: it reads your function's type hints and defaults and constructs the Click commands for you, whereas in Click you declare commands, options and arguments explicitly with decorators. The README describes Typer as based on Python type hints, and the shortest example adds only one import and one function call.

### How does Typer compare with argparse?

argparse is in the standard library and is built procedurally, adding arguments to a parser object one at a time, with nothing derived from type hints. Typer derives the interface from annotations and adds dependencies including shellingham, rich and annotated-doc, so argparse remains the lighter option for scripts with only one or two flags.

## Sources

- [fastapi/typer on GitHub](https://github.com/fastapi/typer)
- [License: MIT](https://github.com/fastapi/typer/blob/master/LICENSE)
- [Project website](https://typer.tiangolo.com/)
- [README](https://github.com/fastapi/typer/blob/master/README.md)
- [Releases](https://github.com/fastapi/typer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fastapi-typer
