Skip to main content

Overview

Flask provides a built-in CLI system based on Click that allows you to create custom commands for your application. The CLI is accessible via the flask command and automatically discovers your application.

Basic Usage

Running the Development Server

flask --app myapp run
The run command starts a local development server with auto-reloading:
# Run with debugging enabled
flask --app myapp --debug run

# Run on a specific host and port
flask run --host 0.0.0.0 --port 8080

# Run with HTTPS
flask run --cert adhoc
flask run --cert cert.pem --key key.pem

Interactive Shell

The shell command opens an interactive Python shell with your app context:
flask shell
This automatically imports your app and makes it available in the shell context.

Routes Command

List all registered routes:
flask routes
flask routes --sort methods
flask routes --all-methods

Application Discovery

Flask uses several methods to locate your application:

1. Using --app Option

flask --app myapp run
flask --app mypackage.mymodule:app run
flask --app myapp:create_app() run

2. Using FLASK_APP Environment Variable

export FLASK_APP=myapp
flask run

3. Auto-Discovery

If no app is specified, Flask looks for:
  • wsgi.py
  • app.py
In these files, Flask searches for:
  • An instance named app or application
  • A factory function named create_app or make_app

Creating Custom Commands

Using the @app.cli.command() Decorator

import click
from flask import Flask

app = Flask(__name__)

@app.cli.command()
def init_db():
    """Initialize the database."""
    click.echo('Initializing database...')
    # Database initialization code
    click.echo('Database initialized!')
Run with:
flask init-db

Commands with Arguments

@app.cli.command()
@click.argument('name')
def greet(name):
    """Greet a user by name."""
    click.echo(f'Hello, {name}!')
flask greet John

Commands with Options

@app.cli.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME COUNT times."""
    for _ in range(count):
        click.echo(f'Hello, {name}!')
flask hello --count 3 --name Alice

Application Context

Use the @with_appcontext decorator to ensure your command runs within the application context:
from flask.cli import with_appcontext
from flask import current_app

@app.cli.command()
@with_appcontext
def show_config():
    """Display application configuration."""
    click.echo(f"Debug mode: {current_app.config['DEBUG']}")
    click.echo(f"Secret key set: {'SECRET_KEY' in current_app.config}")
Commands registered under app.cli automatically have the app context available without needing @with_appcontext.

Command Groups

Organize related commands into groups:
import click
from flask import Flask
from flask.cli import AppGroup

app = Flask(__name__)

user_cli = AppGroup('user')

@user_cli.command('create')
@click.argument('name')
def create_user(name):
    """Create a new user."""
    click.echo(f'Creating user: {name}')

@user_cli.command('delete')
@click.argument('name')
def delete_user(name):
    """Delete a user."""
    click.echo(f'Deleting user: {name}')

app.cli.add_command(user_cli)
flask user create alice
flask user delete bob

Environment Variables

Loading .env Files

Flask automatically loads environment variables from .flaskenv and .env files:
# .flaskenv (checked into version control)
FLASK_APP=myapp
FLASK_DEBUG=0

# .env (not checked into version control)
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://localhost/mydb

Custom .env File

flask --env-file .env.production run

The FlaskGroup Class

For advanced use cases, create a custom CLI group:
import click
from flask import Flask
from flask.cli import FlaskGroup

def create_app():
    app = Flask(__name__)
    # Configure app
    return app

@click.group(cls=FlaskGroup, create_app=create_app)
def cli():
    """Management script for the application."""
    pass

@cli.command()
def custom():
    """A custom command."""
    click.echo('Running custom command')

if __name__ == '__main__':
    cli()

Plugin Commands

Flask can load CLI commands from plugins via entry points:
# setup.py
from setuptools import setup

setup(
    name='flask-my-extension',
    entry_points={
        'flask.commands': [
            'my-command=flask_my_extension.cli:my_command',
        ],
    },
)

ScriptInfo Object

The ScriptInfo object manages application loading:
from flask.cli import ScriptInfo, pass_script_info

@app.cli.command()
@pass_script_info
def info(script_info):
    """Show application info."""
    app = script_info.load_app()
    click.echo(f'App name: {app.name}')
    click.echo(f'Debug mode: {app.debug}')

Best Practices

1. Use Descriptive Help Text

@app.cli.command()
def init_db():
    """Initialize the database schema.
    
    This will create all tables defined in your models.
    WARNING: This will drop all existing tables!
    """
    pass

2. Provide Feedback

@app.cli.command()
def process_data():
    """Process data files."""
    with click.progressbar(range(100)) as bar:
        for _ in bar:
            # Process item
            pass
    click.secho('Processing complete!', fg='green')

3. Handle Errors Gracefully

@app.cli.command()
def deploy():
    """Deploy the application."""
    try:
        # Deployment code
        click.secho('Deployment successful!', fg='green')
    except Exception as e:
        click.secho(f'Deployment failed: {e}', fg='red', err=True)
        raise click.Abort()

4. Use Confirmation for Destructive Operations

@app.cli.command()
@click.confirmation_option(prompt='Are you sure you want to drop the database?')
def drop_db():
    """Drop all database tables."""
    # Drop tables
    click.echo('Database dropped.')

Testing CLI Commands

Use FlaskCliRunner to test your commands:
def test_init_db_command():
    runner = app.test_cli_runner()
    result = runner.invoke(args=['init-db'])
    assert 'Initialized' in result.output
    assert result.exit_code == 0

Common Patterns

Database Initialization

@app.cli.command()
@with_appcontext
def init_db():
    """Clear existing data and create new tables."""
    from myapp.database import db
    db.drop_all()
    db.create_all()
    click.echo('Initialized the database.')

Seed Data

@app.cli.command()
@with_appcontext
def seed():
    """Seed the database with sample data."""
    from myapp.database import db
    from myapp.models import User
    
    users = [
        User(username='alice', email='[email protected]'),
        User(username='bob', email='[email protected]'),
    ]
    db.session.add_all(users)
    db.session.commit()
    click.echo('Database seeded.')

Export Data

import json

@app.cli.command()
@click.argument('output', type=click.File('w'))
@with_appcontext
def export_users(output):
    """Export all users to a JSON file."""
    from myapp.models import User
    users = User.query.all()
    data = [{'username': u.username, 'email': u.email} for u in users]
    json.dump(data, output, indent=2)
    click.echo(f'Exported {len(users)} users.')

Build docs developers (and LLMs) love