Last modified: Nov 02, 2025 By Alexander Williams

Python Typer Custom Shell Completion Guide

Shell completion makes CLI tools user-friendly. It suggests options as users type. Typer provides built-in completion support.

This guide teaches custom completion functions. You will learn to create dynamic suggestions. These adapt to your application's context.

Understanding Shell Completion

Shell completion helps users discover commands. It reduces typing errors. It speeds up command execution.

Typer supports bash, zsh, and fish shells. The framework handles completion automatically. But custom functions add powerful features.

Custom completion provides context-aware suggestions. These can come from databases or APIs. They make your CLI intelligent.

Basic Completion Setup

First, install Typer if not already done. Use pip for installation.


pip install typer

Create a simple CLI application. This demonstrates basic completion.


import typer

app = typer.Typer()

@app.command()
def greet(name: str):
    typer.echo(f"Hello, {name}!")

if __name__ == "__main__":
    app()

Test the basic completion. Install the completion for your shell.


python my_app.py --install-completion

Creating Custom Completion Functions

Custom functions use the typer.Context object. They access command context. They return relevant suggestions.

Define a function that returns completion items. Use the autocompletion parameter. Attach it to your command arguments.


def complete_name(ctx: typer.Context, incomplete: str):
    names = ["Alice", "Bob", "Charlie", "David", "Eve"]
    return [name for name in names if name.startswith(incomplete)]

@app.command()
def greet(
    name: str = typer.Argument(..., help="Name to greet", autocompletion=complete_name)
):
    typer.echo(f"Hello, {name}!")

The function filters names based on input. It shows only matching suggestions.

Dynamic Completion from External Sources

Custom completion can fetch data dynamically. This is useful for file systems or APIs.

Create a function that lists files. It completes file paths in the current directory.


import os

def complete_files(ctx: typer.Context, incomplete: str):
    current_dir = os.getcwd()
    files = []
    for item in os.listdir(current_dir):
        if item.startswith(incomplete):
            files.append(item)
    return files

@app.command()
def process_file(
    filename: str = typer.Argument(..., autocompletion=complete_files)
):
    typer.echo(f"Processing {filename}")

This provides real-time file suggestions. Users can navigate directories easily.

Context-Aware Completion

Use the context object for smarter completion. Access previous arguments. Make suggestions based on command state.

Create a deployment command. Suggest environments based on selected project.


def complete_environment(ctx: typer.Context, incomplete: str):
    # Get the project from context
    project = ctx.params.get("project")
    
    environments = {
        "web-app": ["development", "staging", "production"],
        "api": ["dev", "test", "prod"],
        "database": ["local", "cloud"]
    }
    
    if project in environments:
        return [env for env in environments[project] if env.startswith(incomplete)]
    return []

@app.command()
def deploy(
    project: str = typer.Argument(..., autocompletion=complete_environment),
    environment: str = typer.Argument(..., autocompletion=complete_environment)
):
    typer.echo(f"Deploying {project} to {environment}")

The completion changes based on project selection. This creates intelligent workflows.

Integration with Other Typer Features

Custom completion works well with other Typer features. Combine it with Pydantic models for validation.

Check our Python Typer Pydantic Integration Guide for details.

Use completion with configuration files. Merge CLI arguments with file settings.

Learn more in our Python Typer Config File CLI Argument Merging guide.

Global options work with completion too. Create consistent CLI experiences.

See Python Typer Global Options with Root Callbacks for implementation.

Testing Completion Functions

Test your completion logic thoroughly. Use Typer's testing utilities.

Create unit tests for completion functions. Verify they return correct suggestions.


def test_complete_name():
    # Mock context object
    class MockContext:
        params = {}
    
    ctx = MockContext()
    
    # Test completion with partial input
    result = complete_name(ctx, "A")
    assert "Alice" in result
    assert "Bob" not in result
    
    # Test completion with empty input
    result = complete_name(ctx, "")
    assert len(result) == 5

Testing ensures reliable completion behavior. It catches edge cases early.

Advanced Completion Patterns

Create hierarchical completion for complex commands. Suggest subcommands based on context.

Implement completion for nested command structures. This works well for multi-level CLIs.


def complete_subcommand(ctx: typer.Context, incomplete: str):
    commands = ["init", "deploy", "status", "logs", "cleanup"]
    return [cmd for cmd in commands if cmd.startswith(incomplete)]

@app.command()
def project(
    action: str = typer.Argument(..., autocompletion=complete_subcommand)
):
    typer.echo(f"Executing project {action}")

This pattern helps users discover available actions. It improves command discovery.

Performance Considerations

Completion functions should be fast. Users expect instant suggestions.

Avoid expensive operations in completion. Use caching for external data sources.

Implement lazy loading for large datasets. Load suggestions only when needed.

For performance tips, see our Boost Python Typer Performance with Lazy Imports guide.

Shell-Specific Completion

Different shells have unique features. Customize completion behavior per shell.

Detect the shell type in your function. Return formatted suggestions accordingly.


def shell_aware_completion(ctx: typer.Context, incomplete: str):
    shell = os.environ.get("SHELL", "")
    
    items = ["item1", "item2", "item3"]
    filtered = [item for item in items if item.startswith(incomplete)]
    
    # Add shell-specific formatting
    if "zsh" in shell:
        return [f"{item}:custom_description" for item in filtered]
    elif "fish" in shell:
        return [(item, f"Description for {item}") for item in filtered]
    else:
        return filtered

This provides optimal experience for each shell. Users get native feeling completion.

Error Handling in Completion

Handle errors gracefully in completion functions. Don't let exceptions break the shell.

Wrap external calls in try-except blocks. Return empty list on failure.


def safe_completion(ctx: typer.Context, incomplete: str):
    try:
        # Potentially risky operation
        items = fetch_from_database(incomplete)
        return items
    except Exception:
        # Return empty list on error
        return []

This ensures completion never crashes the shell. It degrades gracefully.

Conclusion

Custom shell completion enhances CLI usability. It makes applications intuitive and efficient.

Start with simple static completion. Progress to dynamic context-aware functions.

Remember performance and error handling. Test across different shell environments.

Combine completion with other Typer features. Create professional command-line tools.

Your users will appreciate the smart suggestions. They will work faster with your CLI.