Last modified: Aug 12, 2025 By Alexander Williams

Python Typer Global State with Context

Python Typer simplifies CLI app development. One powerful feature is managing shared global state. This article explains how to use context objects.

What Are Context Objects in Typer?

Context objects allow sharing data between commands. They provide a clean way to manage global state. This avoids using messy global variables.

Typer's Context class stores this shared data. It passes between commands during execution. Each command can access and modify it.

Why Use Context Objects?

Global variables can cause issues in CLI apps. Context objects offer a better solution. They provide:

  • Clean state management
  • Isolated command contexts
  • Type safety with annotations

For complex apps, context objects are essential. They work well with Typer's async command support.

Basic Context Object Example

Here's a simple example showing context usage:


import typer

app = typer.Typer()

@app.callback()
def main(ctx: typer.Context):
    ctx.obj = {"debug": False}

@app.command()
def run(ctx: typer.Context):
    if ctx.obj["debug"]:
        print("Debug mode enabled")
    else:
        print("Running normally")

if __name__ == "__main__":
    app()


$ python app.py run
Running normally

Modifying Context State

Commands can modify the context. Changes persist for subsequent commands. This example shows state modification:


@app.command()
def debug(ctx: typer.Context, enable: bool):
    ctx.obj["debug"] = enable
    print(f"Debug mode set to {enable}")


$ python app.py debug --enable True
Debug mode set to True
$ python app.py run
Debug mode enabled

Advanced Context Usage

For complex apps, use proper classes instead of dicts. This provides better type safety and organization:


class AppState:
    def __init__(self):
        self.debug = False
        self.verbose = False

@app.callback()
def main(ctx: typer.Context):
    ctx.obj = AppState()

@app.command()
def config(ctx: typer.Context, debug: bool, verbose: bool):
    ctx.obj.debug = debug
    ctx.obj.verbose = verbose

This approach integrates well with Rich integration for better output.

Context in Nested Commands

Context objects work with command groups. All subcommands share the same context:


app = typer.Typer()
subapp = typer.Typer()
app.add_typer(subapp, name="sub")

@subapp.command()
def action(ctx: typer.Context):
    print(f"Debug state: {ctx.obj.debug}")

Best Practices

Follow these tips for effective context usage:

  • Initialize context in the callback
  • Use proper classes for complex state
  • Document expected context structure
  • Combine with error handling

Conclusion

Typer's context objects provide powerful state management. They replace global variables with a clean solution. This makes CLI apps more maintainable and scalable.

For large applications, consider combining contexts with lazy imports to improve performance. Context objects are a key feature for professional Typer development.