Flask Application Factory: Why create_app() Beats a Global App Object
Flask's create_app() factory pattern gives you per-environment app instances, clean pytest fixtures, and no import-time side effects. A worked example plus the circular-import and app-context mistakes to avoid.
10 Jun 2026, 11:10 UTC

The short answer
If your Flask project is more than a single-file prototype, define a create_app() factory function instead of instantiating Flask(__name__) at module level. The factory gives you one app instance per configuration (testing, development, production), eliminates import-time side effects, and makes pytest fixtures trivial. The cost is a small amount of discipline: extensions must be created unbound and attached later with init_app().
What problem the factory actually solves
The classic tutorial pattern creates the app at import time:
app = Flask(__name__)
db = SQLAlchemy(app)
That works until you need a second app instance — for example, a test suite that uses an in-memory SQLite database while your dev server uses PostgreSQL. With a module-level app, importing your code is configuring your app, so tests can't get a clean, differently-configured instance without monkey-patching. The factory inverts this: nothing happens at import time; an app only exists when someone calls the function.
A worked example
Assume Flask 2.x/3.x and Flask-SQLAlchemy 3.x. Project layout:
myapp/
__init__.py # the factory
extensions.py # unbound extension instances
routes.py # a blueprint
config.py # config classes
extensions.py — create extensions without an app:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
config.py:
class Config:
SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"
class TestConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
routes.py:
from flask import Blueprint, current_app
bp = Blueprint("main", __name__)
@bp.get("/health")
def health():
return {"db": current_app.config["SQLALCHEMY_DATABASE_URI"]}
__init__.py — the factory itself:
from flask import Flask
from .extensions import db
from .routes import bp
def create_app(config_object="myapp.config.Config"):
app = Flask(__name__)
app.config.from_object(config_object)
db.init_app(app) # bind AFTER the app exists
app.register_blueprint(bp)
with app.app_context():
db.create_all() # needs an app context
return app
Run it from the project root (no special permissions needed):
export FLASK_APP="myapp:create_app"
flask run
Flask detects that FLASK_APP points to a callable and invokes it. A quick check that everything is wired up: curl http://127.0.0.1:5000/health should return the configured database URI. To confirm the factory is lazy, run python -c "import myapp" — it should succeed without touching the database or binding a port.
Why testing gets easier
Each test session can build its own app:
import pytest
from myapp import create_app
@pytest.fixture()
def app():
return create_app("myapp.config.TestConfig")
@pytest.fixture()
def client(app):
return app.test_client()
def test_health(client):
assert client.get("/health").json["db"].endswith(":memory:")
No global state leaks between tests, and you can run the same suite against a staging config by changing one string.
Common mistakes and limits
- Circular imports. If
extensions.pyor a model module doesfrom myapp import app, the import fails because the app doesn't exist until the factory runs. The fix is the pattern above: importdbfromextensions.pyand useflask.current_appinside request handlers when you need the app. - Calling
db.create_all()without a context. Outside a request, SQLAlchemy doesn't know which app to use. Wrap it inwith app.app_context():as shown, or better, use Flask-Migrate and runflask db upgradeinstead ofcreate_all()for anything real. - Binding extensions too early.
SQLAlchemy(app)at module level re-introduces import-time coupling. Always use the deferredinit_app(app)form; every mainstream Flask extension supports it. - Registering blueprints before the app exists. Blueprint registration is a method on the app instance, so it must happen inside the factory, after
Flask(__name__). - Per-request overhead concerns. There are none — the factory runs once at process start (or once per test), not per request.
When you can skip it
For a throwaway script or a single-endpoint internal tool, the global app is fine. The factory earns its keep the moment you have more than one configuration, a test suite, or CLI commands (flask --app "myapp:create_app" my-command) that need an app built with specific settings. If you're unsure, start with the factory — retrofitting it later means untangling every module that imported the global app.
Verifying the result
flask --app "myapp:create_app" routeslists your blueprint's routes — proves registration happened inside the factory.pytestpasses with the in-memory config — proves configuration is per-instance.python -c "import myapp"exits cleanly with no database file created — proves no import-time side effects.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.