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
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:
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:
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:
Commands with Arguments
@app.cli.command()
@click.argument('name')
def greet(name):
"""Greet a user by name."""
click.echo(f'Hello, {name}!')
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.')