๐ŸŒ
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.
๐ŸŒ
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()
๐ŸŒ
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
As it's based on standard Python types, it "just works". โœจ ยท The typer.CallbackParam is actually just a sub-class of Click's Parameter, so you get all the right completion in your editor.
Author: fastapi
๐ŸŒ
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/โ€ฆ ยท
๐ŸŒ
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
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
Find elsewhere
๐ŸŒ
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 ยท
Author: fastapi
๐ŸŒ
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.
๐ŸŒ
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
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__
๐ŸŒ
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.
๐ŸŒ
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: ...
๐ŸŒ
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 ยท
๐ŸŒ
Typer
typer.tiangolo.com โ€บ reference โ€บ typer
Typer class - Typer
callback( *, cls=Default(None), invoke_without_command=Default(False), no_args_is_help=Default(False), subcommand_metavar=Default(None), chain=Default(False), result_callback=Default(None), context_settings=Default(None), help=Default(None), epilog=Default(None), short_help=Default(None), options_metavar=Default(None), add_help_option=Default(True), hidden=Default(False), deprecated=Default(False), rich_help_panel=Default(None), ) Using the decorator @app.callback, you can declare the CLI parameters for the main CLI application. Read more in the Typer docs for Callbacks.