All Articles
Technical 5 min read

Why Typer?

Typer is a modern library for building CLI applications, created by Sebastián Ramírez (the creator of FastAPI). It leverages Python type hints to automatically generate beautiful command-line interfaces with minimal code.

Key benefits:

  • Intuitive: Uses Python type hints - no new syntax to learn
  • Automatic help: Generates help messages and documentation automatically
  • Scalable: Easy to organize commands into groups for complex applications
  • Fast development: Less boilerplate, more functionality

Quick Setup

Install Typer with a single command:

pip install "typer[all]"

The [all] option includes extras like rich formatting and shell completion.

Building a Task Manager CLI

Let’s build a practical example: a task manager called taskmaster that handles tasks and projects.

Project Structure

taskmaster/
├── __init__.py
├── __main__.py
├── task.py
└── project.py

Step 1: Main Entry Point

Create main.py to make your CLI runnable as a module:

import typer

app = typer.Typer(help="TaskMaster - Your CLI Task Manager")

# Import command groups
from . import task, project

app.add_typer(task.app, name="task")
app.add_typer(project.app, name="project")

if __name__ == "__main__":
app()

Step 2: Task Commands

Create task.py with task-related commands:

import typer
from typing import Optional
from rich.console import Console
from rich.table import Table

app = typer.Typer(help="Manage your tasks")
console = Console()

# Simple in-memory storage (use a database in production)
tasks = []

@app.command()
def create(
title: str,
description: Optional[str] = None,
priority: str = typer.Option("medium", help="Task priority: low, medium, high")
):
"""Create a new task"""
task = {
  "id": len(tasks) + 1,
  "title": title,
  "description": description,
  "priority": priority,
  "done": False
}
tasks.append(task)
console.print(f"✓ Task created: [bold]{title}[/bold]", style="green")

@app.command()
def list():
"""List all tasks"""
if not tasks:
  console.print("No tasks found.", style="yellow")
  return

table = Table(title="Your Tasks")
table.add_column("ID", justify="center")
table.add_column("Title", style="cyan")
table.add_column("Priority", justify="center")
table.add_column("Status", justify="center")

for task in tasks:
  status = "✓" if task["done"] else "○"
  table.add_row(
      str(task["id"]),
      task["title"],
      task["priority"],
      status
  )

console.print(table)

@app.command()
def complete(task_id: int):
"""Mark a task as complete"""
for task in tasks:
  if task["id"] == task_id:
      task["done"] = True
      console.print(f"✓ Task {task_id} marked complete!", style="green")
      return
console.print(f"Task {task_id} not found", style="red")

Step 3: Project Commands

Create project.py for project management:

import typer
from rich.console import Console

app = typer.Typer(help="Manage your projects")
console = Console()

projects = []

@app.command()
def create(name: str, description: str = ""):
"""Create a new project"""
project = {
  "id": len(projects) + 1,
  "name": name,
  "description": description
}
projects.append(project)
console.print(f"✓ Project created: [bold]{name}[/bold]", style="green")

@app.command()
def list():
"""List all projects"""
if not projects:
  console.print("No projects found.", style="yellow")
  return

for project in projects:
  console.print(f"[bold cyan]{project['name']}[/bold cyan]")
  if project["description"]:
      console.print(f"  {project['description']}", style="dim")

Using Your CLI

Run your CLI as a Python module:

# Create tasks
python -m taskmaster task create "Build the API" --priority high
python -m taskmaster task create "Write documentation"

# List tasks
python -m taskmaster task list

# Complete a task
python -m taskmaster task complete 1

# Create projects
python -m taskmaster project create "Web App" "Main website project"
python -m taskmaster project list

# Get help
python -m taskmaster --help
python -m taskmaster task --help

Making It Even Better

1. Interactive Prompts

@app.command()
def create_interactive():
"""Create a task interactively"""
title = typer.prompt("Task title")
description = typer.prompt("Description (optional)", default="")
priority = typer.prompt("Priority", default="medium")
# ... create task

2. Confirmation Dialogs

@app.command()
def delete(task_id: int):
"""Delete a task"""
if typer.confirm(f"Are you sure you want to delete task {task_id}?"):
  # ... delete task
  console.print("Task deleted", style="green")

3. Progress Bars

from rich.progress import track
import time

@app.command()
def sync():
"""Sync tasks with server"""
for _ in track(range(100), description="Syncing..."):
  time.sleep(0.02)
console.print("✓ Sync complete!", style="green")

Scalability Best Practices

Tips for Growing Your CLI

  • Organize by domain: Group related commands in separate files
  • Use type hints: They provide automatic validation and help messages
  • Keep commands focused: One command should do one thing well
  • Add rich output: Use colors, tables, and progress bars for better UX
  • Test your CLI: Typer works great with pytest

Conclusion

Typer makes building CLI applications enjoyable and productive. With its intuitive API based on type hints, you can quickly create professional command-line tools that scale from simple scripts to complex applications.

The key to scalability is organizing commands into logical groups using Typer’s sub-application feature. This keeps your code clean and maintainable as your CLI grows.

Next Steps

  • Add persistent storage with SQLite or JSON files
  • Implement shell completion for better UX
  • Package your CLI with setuptools for easy installation
  • Explore Typer’s documentation at typer.tiangolo.com
HT

Written by Hisham Tariq

Backend Engineer & AI Researcher passionate about building secure, intelligent systems at the intersection of cybersecurity and artificial intelligence. Specializing in Python, FastAPI, and machine learning.