Flask’s built-in CLI is surprisingly powerful, but it’s not always enough. You want to add your own custom commands, the kind that do more than just run the server. Think database migrations, data imports, or custom reporting.
Let’s say you have a Flask app, and you want to add a command to greet a user by name.
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)
This is a standard Flask app. Now, let’s bring in click, the library Flask uses under the hood for its CLI. You’ll typically find click already installed if you have Flask.
To add a custom command, we’ll use the @app.cli.command() decorator. This tells Flask to register the function below it as a CLI command.
# app.py
from flask import Flask
import click
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.cli.command('greet')
def greet_command():
"""Greets the user."""
name = click.prompt('Please enter your name')
click.echo(f'Hello, {name}!')
if __name__ == '__main__':
app.run(debug=True)
Now, if you’re in your project’s root directory (where app.py lives), you can run:
flask --app app.py greet
The flask command is your entry point. --app app.py tells it which Flask application to load. greet is the name of our custom command.
When you run this, it will pause and prompt you:
Please enter your name:
Type your name, say "Alice", and press Enter. You’ll see:
Hello, Alice!
This is click.prompt in action, pausing execution and waiting for user input. click.echo is the preferred way to print output in Click applications; it handles terminal capabilities better than print.
You can also add arguments and options to your commands, just like Flask’s built-in ones. Let’s modify the greet command to accept a name directly as an argument.
# app.py
from flask import Flask
import click
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.cli.command('greet')
@click.argument('name')
def greet_command(name):
"""Greets the user by name."""
click.echo(f'Hello, {name}!')
Now, you can run it like this:
flask --app app.py greet Bob
And the output will be:
Hello, Bob!
The @click.argument('name') decorator tells Click to expect a positional argument named name and pass it to our greet_command function.
What if you want to add an optional greeting message? That’s where options come in.
# app.py
from flask import Flask
import click
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.cli.command('greet')
@click.argument('name')
@click.option('--message', '-m', default='Hello', help='The greeting message.')
def greet_command(name, message):
"""Greets the user by name with an optional message."""
click.echo(f'{message}, {name}!')
Now you have more flexibility:
# Using the default message
flask --app app.py greet Charlie
# Output: Hello, Charlie!
# Specifying a custom message
flask --app app.py greet David --message "Good morning"
# Output: Good morning, David!
# Using the short option flag
flask --app app.py greet Eve -m "Welcome"
# Output: Welcome, Eve!
The @click.option('--message', '-m', default='Hello', help='The greeting message.') decorator defines an option. --message is the long form, -m is the short form, default='Hello' sets a default value if the option isn’t provided, and help provides a description shown when you run flask --app app.py greet --help.
This pattern is extremely useful for tasks like:
- Database Management: Commands to create tables, run migrations (e.g.,
flask --app app.py db migrate,flask --app app.py db upgrade), or seed the database with initial data. - Data Processing: Scripts to import data from CSV files, export reports, or perform background tasks.
- Application Management: Commands to clear caches, reset user passwords, or perform health checks.
The most surprising thing about using Click with Flask is how seamlessly it integrates, making complex CLI tooling feel like a natural extension of your web application. You’re not just building a web server; you’re building a command-line interface for your application’s logic.
When you define commands, you can also group them under a common subcommand. Imagine you have multiple user-related commands. You can create a user group.
# app.py
from flask import Flask
import click
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
# Create a command group for user-related commands
@app.cli.group('user')
def user_group():
"""Manage users."""
pass
# Add a command to the user group
@user_group.command('create')
@click.argument('username')
def create_user(username):
"""Creates a new user."""
click.echo(f"Creating user: {username}")
# In a real app, you'd add user creation logic here
# Add another command to the user group
@user_group.command('delete')
@click.argument('username')
def delete_user(username):
"""Deletes a user."""
click.echo(f"Deleting user: {username}")
# In a real app, you'd add user deletion logic here
Now, your CLI commands are structured:
# Create a user
flask --app app.py user create admin
# Delete a user
flask --app app.py user delete tempuser
This hierarchical structure is powerful for organizing larger applications.
The one thing most people don’t realize is that the app object you pass to click is a Flask instance, and Click’s decorators are applied directly to its cli attribute, which is itself a Click Group. This means you can chain any Click decorators you want onto your Flask app’s CLI commands, allowing for very sophisticated command-line interfaces without leaving the Flask ecosystem.
The next step is often integrating these commands with external libraries for more complex tasks, such as interacting with databases via SQLAlchemy or managing asynchronous operations.