Flask Application Factory: When to Replace a Module-Level App Object
Decide whether to keep a single Flask app object or switch to a create_app() factory for blueprints, extensions, and isolated test configs.
10 May 2026, 21:16 UTC

The problem and the useful takeaway
You start with app = Flask(__name__) at the top of app.py. It works until a blueprint module imports that same app to register a route, and app.py imports the blueprint. Python raises an import error because neither module has finished loading. The same global object also makes it awkward to run one configuration for tests and another for production inside the same pytest session.
Use Flask’s application factory pattern—a create_app() function that builds and returns a new Flask instance—when your project uses blueprints, extensions such as Flask-SQLAlchemy, or multiple configuration profiles. Keep a module-level app only for single-file prototypes or one-off scripts.
Decision and constraints
Choose the factory pattern if any of the following are true:
- Your routes are split across blueprints that need to register with the app.
- You initialize extensions with
init_app(app)and want to avoid binding them at import time. - Tests need separate configuration objects without sharing state between cases.
- You want to create more than one app instance in the same process, for example a testing app and a development app.
A module-level Flask(__name__) remains acceptable when the entire application lives in one module and you never need a second instance.
Supported options compared
| Option | Best for | Configuration isolation | Testing | Circular import risk |
|---|---|---|---|---|
Module-level app = Flask(__name__) | Single-file apps, prototypes | Limited; one object per process | Harder; config is global | Higher when blueprints import the app |
Application factory create_app() | Blueprints, extensions, multiple configs | Strong; new instance per call | Easier with pytest fixtures | Lower if blueprint imports are deferred |
Trade-offs
The factory adds a small layer of indirection. Code that runs while the factory is building the app cannot use request-specific proxies such as request or session, because no request exists yet. Database initialization or extension setup outside a request requires an explicit app context, typically written as with app.app_context():.
A module-level app is simpler to read in a tiny script, but it forces every blueprint and extension to share the same object. That makes parallel test configurations and multiple app instances impractical.
Implementation: a create_app() factory
Assume a package layout like this:
project/
app/
__init__.py
db.py
auth.py
blog.py
tests/
test_app.py
The factory lives in app/__init__.py. Blueprints and extensions are imported inside the function so they do not see the app before it exists:
# app/__init__.py
import os
from flask import Flask
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"),
DATABASE=os.path.join(app.instance_path, "app.sqlite"),
)
if test_config is None:
app.config.from_pyfile("config.py", silent=True)
else:
app.config.from_mapping(test_config)
try:
os.makedirs(app.instance_path)
except OSError:
pass
# Extensions bind here, inside the factory.
from . import db
db.init_app(app)
# Blueprints are imported here to avoid circular imports.
from . import auth
app.register_blueprint(auth.bp)
from . import blog
app.register_blueprint(blog.bp)
@app.route("/hello")
def hello():
return "Hello, Factory!"
return app
db.init_app(app) is called after app exists, so the extension is bound to this instance only. If you later call create_app() again with {'TESTING': True}, you receive a separate app with its own configuration.
Validation
Run this from the project root with your virtual environment activated:
python -c "from app import create_app; app = create_app({'TESTING': True}); client = app.test_client(); print(client.get('/hello').status_code)"
Expected check: the command prints 200. A risk is an ImportError if db.py, auth.py, or blog.py import app at module level instead of inside create_app().
A pytest fixture that isolates configuration per test looks like this:
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app({"TESTING": True})
with app.test_client() as client:
with app.app_context():
yield client
The app.app_context() block makes the application available to extensions or database operations that run outside an actual HTTP request.
Confirm there is no duplicate module-level application object:
grep -R "Flask(__name__)" --include="*.py" .
The only match should be inside create_app(), or there should be no match at all if every instance is created by the factory.
Limitations and how to check the result
The factory pattern does not fix circular imports by itself. If a blueprint module imports the app object at its top level, moving the app into create_app() will not help; move that import inside the factory or register routes without referencing the app directly.
Request proxies such as request, session, and current_app are unavailable before an app context is pushed. Do not call them at the top level of create_app().
Teardown handlers and error handlers registered in the factory apply only to the instance returned by that call. Tests that build multiple apps must not assume handlers leak between instances.
Practical result check: start a Python shell, create two apps with different TESTING values, and assert that app.config["TESTING"] differs between them. If both instances share the same configuration, the factory is not isolating state as intended.