Stop Creating a Global Flask App: Use an Application Factory Instead
A Flask application factory defers app creation to a function, avoiding global state and making configuration and testing explicit. See a minimal factory with blueprints and late-bound extensions.
12 Feb 2026, 16:36 UTC

Importing a Flask app at module scope locks in configuration before you know the environment. That means tests share state, CLI commands trigger import side effects, and changing config requires editing globals. The useful takeaway is to defer app creation to a function that returns a fresh Flask instance with explicit configuration.
An application factory is a function, typically named create_app, that builds and returns a Flask app. It moves creation from import time to call time. That lets you pass configuration in, create isolated instances for testing, and initialize extensions after the app exists.
The global app problem
With a module-level app = Flask(__name__), any import of that module creates the app and runs top-level code. Blueprints that import views which import the app can create circular imports. Configuration loaded at import time is hard to override for tests or different deployments. Mutable global state leaks between tests because the same app object is reused.
A factory removes the global. The app only exists inside the function call that needs it.
How a factory defers creation
The factory receives parameters such as an environment name or a config dict and returns a configured app. Extensions that support late binding expose init_app. Instead of db = SQLAlchemy(app) at import time, you create the extension object once and call db.init_app(app) inside the factory.
Blueprints are route groups with their own URL prefix and templates. They are created without an app and registered inside the factory. This keeps route modules decoupled from app creation.
A minimal factory with blueprints and late-bound extensions
Run this from the project root with Python 3 and no special permissions. Adjust the package layout placeholders to match your project.
# app/__init__.py
def create_app(config_name='development'):
from flask import Flask
app = Flask(__name__)
# Configuration loaded inside the factory, not at import
configs = {
'development': {'DEBUG': True},
'testing': {'TESTING': True, 'DEBUG': False},
'production': {'DEBUG': False}
}
app.config.from_mapping(configs.get(config_name, {}))
# Late-bound extension
from . import db
db.init_app(app)
# Blueprint registration deferred until app exists
from .users import bp as users_bp
app.register_blueprint(users_bp, url_prefix='/users')
return app
# app/users.py
from flask import Blueprint
bp = Blueprint('users', __name__)
@bp.get('/hello')
def hello():
return 'hello'
Creating an app for a test:
from app import create_app
app = create_app('testing')
client = app.test_client()
resp = client.get('/users/hello')
assert resp.status_code == 200
Expected checks: app.config['TESTING'] is True for the testing instance and False for a development instance created with a different argument. The test client should reach blueprint routes without importing the app at module scope.
Risk: forgetting to call init_app inside the factory leaves the extension unbound and raises errors when accessed in a request context. Circular imports can still occur if a blueprint module imports the factory at top level; keep imports inside functions.
Trade-offs and how to verify it works
The factory adds indirection. You must pass the app object explicitly to code that needs it, and extensions must be initialized inside the factory. Tests need a fresh app per test to avoid state leakage.
A practical verification is to create two apps with different config names and assert their configs differ without mutating module globals. You can also check that a blueprint route is reachable via the test client and that the extension is accessible in an application context.
Limitations remain: the pattern does not prevent logic errors inside blueprints, and complex factories can grow long. Keep configuration loading deterministic and avoid importing the factory from modules that the factory itself imports.
Actionable next step: replace a module-level app with create_app, move all app.config and register_blueprint calls inside it, and initialize extensions with init_app. Then create app instances explicitly in your CLI entry point and tests. This gives reproducible configuration and isolated test instances without global state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.