๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ options โ€บ callback-and-context
CLI Option Callback and Context - Typer
In some occasions you might want to have some custom logic for a specific CLI parameter (for a CLI option or CLI argument) that is executed with the value received from the terminal. In those cases you can use a CLI parameter callback function. For example, you could do some validation before the rest of the code is executed. ... from typing import Annotated import typer app = typer.Typer() def name_callback(value: str): if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value @app.command() def main(name: Annotated[str | None, typer.Option(callback=name_callback)] = None): print(f"Hello {name}") if __name__ == "__main__": app()
๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ commands โ€บ callback
Typer Callback - Typer
You can also add a callback just to add the documentation in the docstring. It can be convenient especially if you have several lines of text, as the indentation will be automatically handled for you: ... import typer app = typer.Typer() @app.callback() def callback(): """ Manage users CLI app.
๐ŸŒ
Jacob Kaplan-Moss
jacobian.org โ€บ til โ€บ common-arguments-with-typer
Common Arguments With Typer - Jacob Kaplan-Moss
Callbacks are what Typer uses to implement groups or common options. @cli.callback() lets me declare the pinboard_token as a common option, and I can do any error checking (here, just making sure it exists) in the callback.
๐ŸŒ
GitHub
github.com โ€บ fastapi โ€บ typer โ€บ blob โ€บ master โ€บ docs โ€บ tutorial โ€บ options โ€บ callback-and-context.md
typer/docs/tutorial/options/callback-and-context.md at master ยท fastapi/typer
You don't have to worry about their names, their order, etc. As it's based on standard Python types, it "just works". โœจ ยท You can get the typer.Context and the typer.CallbackParam simply by declaring a function parameter of each type.
๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ subcommands โ€บ callback-override
Sub-Typer Callback Override - Typer
Here we had defined a callback when creating the typer.Typer() sub-app, but then we override it with a new callback with the function user_callback().
๐ŸŒ
Typer
typer.tiangolo.com โ€บ reference โ€บ parameters
Parameters - Typer - Tiangolo.com
**Example** ```python @app.command() ... Annotated[ Callable[..., Any] | None, Doc( """ Add a callback to this CLI Option, to execute additional logic after its value was received from the terminal....
๐ŸŒ
Calmcode
calmcode.io โ€บ course โ€บ typer โ€บ callbacks
Calmcode - typer: Callbacks
import typer from typing import List from pathlib import Path app = typer.Typer(name="demo", add_completion=False, help="This is a demo app.") def check_file_exists(paths): for p in paths: if not p.exists(): print(f"The path you've supplied {p} does not exist.") raise typer.Exit(code=1) return paths @app.command() def word_count(paths: List[Path] = typer.Argument(..., help="The file to count the words in.", callback=check_file_exists)): """Counts the number of words in a file""" for p in paths: texts = p.read_text().split("\n") n_words = len(set(w for t in texts for w in t.split(" "))) print(f"In total there are {n_words} words in {p}.") if __name__ == "__main__": app()
๐ŸŒ
Medium
medium.com โ€บ @hayata-yamamoto โ€บ introduction-to-typer-a-powerful-cli-tool-for-python-developers-780c9fd87c19
Introduction to Typer: A Powerful CLI Tool for Python Developers | by Hayata Yamamoto | Medium
June 5, 2025 - import time from typing import Optional import typer from rich import print, progress, prompt app = typer.Typer() # Callback definition: https://typer.tiangolo.com/tutorial/commands/callback/ @app.callback(help="App callback definition") def callback( arg1: str = typer.Argument(..., help="Required argument for the entire app"), option1: bool = typer.Option(default=False, help="Optional argument for the entire app"), ) -> None: print(f"{arg1=}") print(f"{option1=}") # Required arguments: https://typer.tiangolo.com/tutorial/commands/arguments/โ€ฆ ยท
Top answer
1 of 2
11

This is addressed in the documentation:

But as those CLI parameters are handled by each of those commands, they don't allow us to create CLI parameters for the main CLI application itself.

But we can use @app.callback() for that.

It's very similar to @app.command(), but it declares the CLI parameters for the main CLI application (before the commands):

To do what you want, you could write something like this:

import typer
from typing import Optional

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        typer.echo(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


app = typer.Typer(
    add_completion=False,
)


@app.callback()
def common(
    ctx: typer.Context,
    version: bool = typer.Option(None, "--version", callback=version_callback),
):
    pass


@app.command()
def main() -> None:
    pass


@app.command()
def foo() -> None:
    pass


if __name__ == "__main__":
    app()

Which gives us:

$ python typertest.py --help
Usage: typertest.py [OPTIONS] COMMAND [ARGS]...

Options:
  --version
  --help     Show this message and exit.

Commands:
  foo
  main
2 of 2
0

This https://github.com/fastapi/typer/issues/52#issuecomment-629918447 worked for me with typer==0.16.0

from __future__ import annotations

import re
from pathlib import Path

import typer

__version__ = "0.1.1"


cli = typer.Typer()


@cli.command(name="bump")
def bump_version() -> None:
    """Bump up version of this script"""
    typer.secho(f"Origin version: {__version__}", fg=typer.colors.BLUE)
    version_parts = [int(i) for i in __version__.split(".")]
    version_parts[-1] += 1
    new_version = ".".join(map(str, version_parts))
    me = Path(__file__)
    text = me.read_text("utf-8")
    pattern = re.compile(
        rf'(?P<prefix>__version__\s*=\s*[\'"]){__version__}(?P<suffix>[\'"])'
    )
    new = pattern.sub(rf"\g<prefix>{new_version}\g<suffix>", text)
    me.write_text(new)
    typer.secho(
        f"Updated {me.name} with new version: {new_version}", fg=typer.colors.MAGENTA
    )


def version_callback(value: bool) -> None:
    if value:
        typer.echo("My Awesome Cli Version: " + typer.style(__version__, bold=True))
        raise typer.Exit()


@cli.callback()
def main(
    version: bool = typer.Option(
        None,
        "--version",
        "-V",
        callback=version_callback,
        is_eager=True,
        help="Show the version of this script",
    ),
) -> None:
    pass


if __name__ == "__main__":
    cli()

Result:

$ python main.py -V
My Awesome Cli Version: 0.1.1
$ python main.py --version
My Awesome Cli Version: 0.1.1
$ python main.py --help

 Usage: main.py [OPTIONS] COMMAND [ARGS]...

โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ --version             -V        Show the version of this script                                            โ”‚
โ”‚ --install-completion            Install completion for the current shell.                                  โ”‚
โ”‚ --show-completion               Show completion for the current shell, to copy it or customize the         โ”‚
โ”‚                                 installation.                                                              โ”‚
โ”‚ --help                          Show this message and exit.                                                โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
โ•ญโ”€ Commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ bump   Bump up version of this script                                                                      โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

$ python main.py bump
Origin version: 0.1.1
Updated main.py with new version: 0.1.2
$ python main.py --version
My Awesome Cli Version: 0.1.2
๐ŸŒ
GitHub
github.com โ€บ fastapi โ€บ typer โ€บ discussions โ€บ 700
[BUG] Double callback value from CLI Option typer.Option() callback argument ยท fastapi/typer ยท Discussion #700
I commit to help with one of those options ๐Ÿ‘† ยท import typer from typing_extensions import Annotated app = typer.Typer() def new_callback(title): print("callback run !") return title @app.command() def create( title: Annotated[str, typer.Option(callback=new_callback, prompt="Enter a title")], description: Annotated[str, typer.Option(prompt="Description")] = ""): print('create function') @app.command() def delete( title: Annotated[str, typer.Option(prompt="Enter a title")], description: Annotated[str, typer.Option(prompt="Description")] = ""): print('delete function') if __name__ == '__main__': app() when i run python3 main.py create and enter the cli options value interactively, the callback executed twice ยท
Find elsewhere
๐ŸŒ
Blue Book
lyz-code.github.io โ€บ blue-book โ€บ typer
Typer - The Blue Book
from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool) -> None: """Print the version of the program.""" if value: print(f"Awesome CLI Version: {__version__}") raise typer.Exit() def main( version: Optional[bool] = typer.Option( None, "--version", callback=version_callback, is_eager=True ), ) -> None: ...
๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ commands โ€บ context
Using the Context - Typer
The same way, in commands or in the main Typer callback you can access the context by declaring a function parameter of type typer.Context.
๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ options โ€บ version
Version CLI Option, is_eager - Typer
But now let's say that the --name CLI option that we declared before --version is required, and it has a callback that could exit the program: ... from typing import Annotated import typer __version__ = "0.1.0" app = typer.Typer() def version_callback(value: bool): if value: print(f"Awesome CLI Version: {__version__}") raise typer.Exit() def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") return name @app.command() def main( name: Annotated[str, typer.Option(callback=name_callback)], version: Annotated[ bool | None, typer.Option("--version", callback=version_callback) ] = None, ): print(f"Hello {name}") if __name__ == "__main__": app()
๐ŸŒ
GitHub
github.com โ€บ fastapi โ€บ typer โ€บ issues โ€บ 373
Allow `--help` even if callback has required options / arguments ยท Issue #373 ยท fastapi/typer
March 23, 2022 - If the callback has a required option, running a command under this callback with the --help option will give a "missing option" error. code.py --help # works code.py cmd --help # missing option '--var' Here's why I think this behavior is unintended: On typer 0.3.2, regular arguments defined in the callback are not required when passing --help to commands under the callback.
Author: fastapi
๐ŸŒ
GitHub
gist.github.com โ€บ harkabeeparolus โ€บ a6e18b1f4f4f938f450090c5e7523f68
Typer โ€” my attempt at reference documentation
callback -- provide a callback function here, instead of using @app.callback() decorator. add_completion=False -- disable the Typer completion system, and remove the options from the help text.
๐ŸŒ
Opensciencelabs
opensciencelabs.org โ€บ blog โ€บ typer-a-python-library-for-building-cli-applications
Typer: A Python Library for Building CLI Applications ยท Open Science Labs
5 days ago - %%writefile ecommerce.py import typer from typer import Context, Option app = typer.Typer(help="Operations for e-commerce.") app_user = typer.Typer(help="Operations for user model.") app_product = typer.Typer(help="Operations for product model.") app.add_typer(app_user, name="user") app.add_typer(app_product, name="product") @app.callback(invoke_without_command=True) def main( ctx: Context, version: bool = Option( None, "--version", "-v", is_flag=True, help="Show the version and exit.", ), ) -> None: """Process envers for specific flags, otherwise show the help menu.""" if version: __version__
๐ŸŒ
CloudBytes
cloudbytes.dev โ€บ home โ€บ snippets โ€บ set the default command in python typer cli
Set the default command in Python Typer CLI ยท CloudBytes/dev
October 1, 2021 - Typer has a callback functionality allows the developer to create CLI parameters for the main CLI application itself.
๐ŸŒ
Pybites Platform
pybitesplatform.com โ€บ bites โ€บ using-typer-callbacks-to-create-command-line-interface-cli-parameters
Using typer callbacks to create command line interface (cli ...
Without a feature like that, one could potentially have lots of redundant code that initializes the same arguments and options for multiple commands. Luckily, Typer Callback serves exactly this purpose.
๐ŸŒ
Typer
typer.tiangolo.com โ€บ tutorial โ€บ subcommands โ€บ name-and-help
SubCommand Name and Help - Typer
If you pass a callback parameter while creating a typer.Typer(callback=some_function) it will be used to infer the help text.
๐ŸŒ
Stack Overflow
stackoverflow.com โ€บ questions โ€บ 75288965 โ€บ how-to-get-unlimited-multiple-options-from-typer-option-with-custom-devider
python - How to get unlimited multiple options from typer.Option with custom devider - Stack Overflow
to have an option with unlimited values, you can get a string and split it by a separator and get a list. You can try this: Copyimport typer app = typer.Typer() @app.callback() def main(): pass @app.command() def add(elements: str = typer.Option(..., help="List of elements separated by a comma")): elements_splited = [element.strip() for element in elements.split(",")] typer.echo(elements_splited) if __name__ == "__main__": app() So : python main.py add --elements "el1, el2, el3, el4" You will get: ['el1', 'el2', 'el3', 'el4'] Share ยท