Skip to main content

Typer Arguments & Options: Complete Parameter Guide

Typer arguments and options are the interface between your CLI and its users. Understanding how to define, validate, and constrain parameters is essential for building robust tools. This article covers every parameter type Typer supports, from simple strings to complex lists and custom validators.

Arguments vs. Options: The Core Distinction

Arguments are positional parameters (e.g., myapp create project_name), while options are named flags (e.g., myapp --output results.txt). Typer treats all function parameters as named options by default unless you explicitly make them positional arguments using typer.Argument().

import typer

app = typer.Typer()

@app.command()
def deploy(
service_name: str = typer.Argument(..., help="Service to deploy"),
env: str = typer.Option("staging", help="Target environment"),
):
"""Deploy a service to an environment."""
typer.echo(f"Deploying {service_name} to {env}")

if __name__ == "__main__":
app()

Run with: python deploy.py myapp --env production. The service_name is positional (required), and env is a named option with a default.

Required vs. Optional Parameters

Required parameters use typer.Argument(...) with ellipsis, which tells Typer to enforce the argument:

import typer
from typing import Optional

app = typer.Typer()

@app.command()
def process_file(
input_file: str = typer.Argument(..., help="Input file path"),
output_file: Optional[str] = typer.Option(None, help="Output file (optional)"),
):
"""Process a file and optionally save results."""
typer.echo(f"Processing: {input_file}")
if output_file:
typer.echo(f"Saving to: {output_file}")

if __name__ == "__main__":
app()

Using Optional[str] with a None default makes the parameter optional; the type hint Optional tells Typer and type checkers that None is allowed.

Parameter Types and Automatic Conversion

Typer converts CLI string inputs to Python types automatically based on your type hints:

import typer
from typing import List

app = typer.Typer()

@app.command()
def create_user(
name: str,
age: int = typer.Option(..., help="User age in years"),
active: bool = typer.Option(True, help="Is active user"),
tags: List[str] = typer.Option([], help="User tags"),
):
"""Create a user with metadata."""
typer.echo(f"Name: {name}, Age: {age}, Active: {active}")
typer.echo(f"Tags: {', '.join(tags) if tags else 'None'}")

if __name__ == "__main__":
app()

Run: python user.py John --age 30 --tags frontend --tags backend. Typer parses "30" as integer, converts the boolean flag, and collects multiple --tags into a list.

Enumerations for Restricted Choices

Enums enforce that a parameter must be one of a fixed set of values:

import typer
from enum import Enum

class Environment(str, Enum):
dev = "dev"
staging = "staging"
prod = "prod"

app = typer.Typer()

@app.command()
def deploy(
env: Environment = typer.Option(Environment.staging),
):
"""Deploy to a specific environment."""
typer.echo(f"Deploying to {env.value}")

if __name__ == "__main__":
app()

Run: python deploy.py --env prod. If the user enters --env invalid, Typer shows an error and lists valid options in the help.

Validation with Callbacks

Custom validation logic runs via callback functions:

import typer
from typing import Optional

app = typer.Typer()

def validate_port(value: int) -> int:
"""Ensure port is in valid range."""
if not (1 <= value <= 65535):
raise typer.BadParameter(f"Port must be 1-65535, got {value}")
return value

@app.command()
def start_server(
port: int = typer.Option(8000, callback=validate_port),
):
"""Start server on a port."""
typer.echo(f"Starting on port {port}")

if __name__ == "__main__":
app()

Callbacks receive the parameter value and should return the validated value or raise typer.BadParameter() with a user-friendly message.

File and Path Parameters

Typer includes special types for files and paths:

import typer
from pathlib import Path
from typing import Optional

app = typer.Typer()

@app.command()
def read_config(
config_path: Path = typer.Argument(
Path("config.yaml"),
help="Config file path",
),
output: Optional[Path] = typer.Option(None),
):
"""Read and display config."""
if config_path.exists():
typer.echo(f"Found config: {config_path.read_text()}")
else:
typer.echo(f"Config not found: {config_path}", err=True)
raise typer.Exit(code=1)

if __name__ == "__main__":
app()

Path objects give you .exists(), .read_text(), and other Path methods directly, plus automatic validation that the file exists (if you set click_type=typer.File() for more strict checking).

Comparison Table: Argument vs. Option

FeatureArgumentOption
SyntaxPositional (myapp value)Named (myapp --name value)
Definitiontyper.Argument(...)typer.Option(...)
Required?Yes by defaultNo; requires default
Help displayShows in usage lineShows in Options section
Multiple valuesMust use List[T] typeCan use List[T] with --opt val repeating
Best forPrimary inputsOptional config, flags

Key Takeaways

  • Use typer.Argument() for positional, required parameters; typer.Option() for named, optional ones.
  • Type hints (int, bool, List[str]) enable automatic conversion and validation.
  • Optional[T] with a None default makes parameters truly optional.
  • Enum types restrict values to a predefined set; Typer auto-generates help showing valid choices.
  • Callback functions provide custom validation; raise typer.BadParameter() for errors.
  • Path and file types handle file operations naturally with automatic existence checks.

Frequently Asked Questions

How do I make a flag like --verbose that doesn't require a value?

Use is_flag=True in typer.Option(): verbose: bool = typer.Option(False, is_flag=True). Users then run myapp --verbose without a value.

Can I have multiple required arguments?

Yes. Each typer.Argument(...) without a default is required. Order matters: define positional arguments in the order users should provide them.

What if I need an argument that accepts a file I can read?

Use typer.File() type: input_file: typer.FileText = typer.Argument(...). Typer opens the file and passes the file object directly.

How do I handle arguments with dashes, like --my-option?

Use underscores in Python (my_option: str = typer.Option(...)); Typer converts them to dashes automatically in the CLI.

Can I mark a parameter as deprecated?

Yes. Use typer.Option(..., deprecated=True). Typer still accepts it but notes in help that it's deprecated, encouraging users to migrate.

Further Reading