Custom CLI Commands (flask CLI)
A custom CLI command lets you run app tasks — seeding data, creating an admin, clearing a cache — from the terminal with flask <name> , with full access to your app.
Learn Custom CLI Commands (flask CLI) in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll build Click commands with arguments and options, run them in Python, register them on a Flask app with access to its config, and meet flask db .
Flask's CLI is powered by Click . A command is just a function decorated with @click.command() . You add arguments (required, positional) with @click.argument and options (optional, named, with defaults) with @click.option . Click handles parsing and help text for you.
The runnable example below defines a hello command and invokes it with Click's test runner — no real terminal needed. Notice how the option's default applies when you don't pass --greeting .
hello Ada uses the default greeting; hello Bob --greeting Hi overrides it. The argument is required, the option is not.
A classic use is a seed command that fills your database with sample data for development. Options can declare a type so Click converts and validates the input — passing --count 5 arrives as the integer 5 , and a non-number is rejected with a helpful error.
The runnable example seeds an in-memory list. Run it twice with different counts to see the typed option in action.
The big payoff of @app.cli.command is that Flask runs your command inside an application context . That means current_app , its config, and app-bound extensions (like the database) are available automatically — exactly as they are in a request.
The runnable example registers a create-admin command that reads current_app.config , and invokes it with Flask's own CLI test runner (which pushes the app context for you).
Complete the command below. Replace each ___ so it takes a required word and an optional --times .
The CLI doesn't know where your app is. Set FLASK_APP=app.py (or use an app factory in create_app ) so flask <command> can import it.
You accessed current_app or db in a plain @click.command not registered on the app. Register it with @app.cli.command so Flask pushes the context for you.
Build a command that reports user counts, with a flag to filter.
Lesson complete — your app has its own toolbox!
You built Click commands with arguments, typed options, and flags, registered them on a Flask app with @app.cli.command , used current_app.config inside a command, and met flask db .
🚀 Up next: Signals (Blinker) — decouple parts of your app with publish/subscribe events.
Practice quiz
Which library powers the flask CLI under the hood?
- argparse
- Click
- Typer
- docopt
Answer: Click. Flask's CLI is built on Click.
How do you register a custom command on a Flask app?
- @app.command('name')
- @click.flask('name')
- @app.cli.command('name')
- @app.register('name')
Answer: @app.cli.command('name'). Decorate with @app.cli.command('name') to register a command.
Which Click decorator declares a required positional input?
- @click.argument('x')
- @click.option('--x')
- @click.flag('x')
- @click.input('x')
Answer: @click.argument('x'). @click.argument('x') is a required positional argument.
Which Click decorator declares an optional named input?
- @click.argument('--x')
- @click.required('x')
- @click.positional('x')
- @click.option('--x', default=...)
Answer: @click.option('--x', default=...). @click.option('--x', default=...) is an optional named input.
Why can a @app.cli command use current_app and db directly?
- Flask runs it inside an application context
- Click imports them globally
- It runs in a request context
- Flask disables contexts for the CLI
Answer: Flask runs it inside an application context. Flask pushes an application context before running an @app.cli command.
Which call prints output from a Click command?
- print()
- click.echo('...')
- sys.stdout('...')
- click.print('...')
Answer: click.echo('...'). click.echo() prints output from a command.
How do you make an option a boolean flag?
- @click.option('--v', bool=True)
- @click.flag('--v')
- @click.option('--v', is_flag=True)
- @click.bool('--v')
Answer: @click.option('--v', is_flag=True). @click.option('--v', is_flag=True) makes a boolean flag.
Which command group is added by the Flask-Migrate extension?
- flask run
- flask shell
- flask routes
- flask db
Answer: flask db. flask db comes from Flask-Migrate (built on Alembic).
What error appears if Flask can't find your app for the CLI?
- Could not locate a Flask application
- 404 Not Found
- Working outside of request context
- Module not found: click
Answer: Could not locate a Flask application. Set FLASK_APP (or use a create_app factory) so the CLI can import the app.
What does @click.option('--count', type=int) do with '--count 5'?
- Keeps it the string '5'
- Raises an error
- Converts it to the integer 5
- Ignores the value
Answer: Converts it to the integer 5. Click converts the input to the declared type, so it becomes int 5.