Flask Application Factory Pattern: create_app() and Blueprint Registration
A module-level Flask app causes circular imports and blocks test configs. Use an application factory instead.
06 Sept 2026, 23:01 UTC

The problem with a module-level app object
Many Flask tutorials start with app = Flask(__name__) at the top of __init__.py. That works for a single file, but it creates two problems as soon as you split the project into blueprints and models:
- If a blueprint or model module imports
app, and__init__.pyimports those modules, Python can hit a circular import before the application object exists. - Because the object is created at import time, you cannot easily build a second instance with different settings for tests, staging, or production.
What the application factory does
An application factory is a function—commonly named create_app()—that constructs and returns a configured Flask instance. The app is not created until the function is called, so imports can finish first. Inside the factory you:
- load configuration from objects, files, or environment variables;
- initialize extensions with their
init_app()method; - register blueprints that contain routes and view functions;
- register CLI commands and error handlers.
This pattern is well established in Flask and is the default structure used by larger, testable projects. This guide assumes Flask 2.3 or later and Flask-SQLAlchemy 3.x. In Flask-SQLAlchemy 3, database operations require an active application context.
Worked example: project layout and factory
Assume a package named mypackage. The following layout keeps extensions, models, and routes separate.
mypackage/
├── __init__.py
├── extensions.py
├── models.py
├── config.py
└── blueprints/
├── __init__.py
└── main.py
extensions.py holds extension objects without binding them to an app yet:
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
models.py imports only the extension object, not the app:
from .extensions import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
blueprints/main.py defines routes on a Blueprint—a reusable set of routes that is registered on an app later:
from flask import Blueprint
bp = Blueprint("main", __name__)
@bp.route("/")
def index():
return "Hello from the application factory"
The factory in __init__.py assembles everything:
import os
from flask import Flask
from .extensions import db, migrate
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY=os.environ.get("SECRET_KEY", "dev"),
SQLALCHEMY_DATABASE_URI=os.environ.get(
"DATABASE_URL", "sqlite:///app.db"
),
)
if test_config is not None:
app.config.from_mapping(test_config)
else:
app.config.from_pyfile("config.py", silent=True)
db.init_app(app)
migrate.init_app(app, db)
from .blueprints.main import bp as main_bp
app.register_blueprint(main_bp)
@app.cli.command("init-db")
def init_db():
with app.app_context():
db.create_all()
return app
Key details in this example:
Flask(__name__, instance_relative_config=True)tells Flask to look for instance files relative to theinstance/folder.db.init_app(app)andmigrate.init_app(app, db)bind the extension objects to the newly created instance. This is required because the extension objects were created at module level, before any app existed.- The blueprint import happens inside
create_app(), after the app object exists, which avoids circular imports. with app.app_context()pushes an application context—the state that lets Flask look up the current app and database outside an active HTTP request.
Running and verifying the factory
Run commands from the project root as a normal user. Replace mypackage with your package name.
Confirm the factory creates an app and registers its CLI command:
flask --app mypackage:create_app init-db
Expected check: Flask accepts mypackage:create_app as the application import string and runs init-db. The command creates tables for models defined on db.Model. Risk: it writes to the database URI in your configuration, so use a development or test database first.
Inspect registered blueprints without starting a server:
python -c "from mypackage import create_app; app = create_app(); print(sorted(app.blueprints))"
Expected check: the output includes 'main'. This is a read-only inspection.
For tests, call the factory with a test configuration:
import pytest
from mypackage import create_app, db
@pytest.fixture
def app():
app = create_app({
"TESTING": True,
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:",
})
with app.app_context():
db.create_all()
yield app
Because the factory accepts test_config, each test can use a fresh in-memory database without touching your development file.
Limits and common mistakes
| Mistake | Why it fails | Fix |
|---|---|---|
Creating app = Flask(__name__) at module level | Blueprints or models that import app can cause circular imports. | Move app creation into create_app(). |
Accessing current_app outside a request or app context | Flask raises an error because no application is active. | Wrap the code in with app.app_context():. |
| Storing request-specific data on an extension object | Extension objects are shared across requests; data leaks between users. | Use Flask's g object inside a request. |
Committing SECRET_KEY to version control | Anyone with repository access can forge sessions. | Load it from an environment variable. |
Using db.create_all() as a migration tool | It creates missing tables but does not alter existing columns. | Use Flask-Migrate for schema changes. |
The factory pattern adds a small amount of indirection. A single-file prototype may not need it, but once you have blueprints, models, and multiple deployment environments, the factory is the safer default. The built-in Flask development server is not suitable for production; deploy with a WSGI server (Web Server Gateway Interface, the standard between web servers and Python web apps) such as Gunicorn or uWSGI behind a reverse proxy.
Practical check
After adding the factory, run flask --app mypackage:create_app init-db and confirm the command completes without import errors. Then open a Python shell, call create_app(), and verify that app.blueprints contains your registered blueprints. If both checks pass, the factory is wired correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.