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.
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 ยท
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
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.
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 ยท