Flask Application Factory vs Global App: A Decision Guide for Testable Configs
Choose create_app factory over a module-level Flask instance when you need distinct dev/test/prod configs, isolated test apps, and reusable blueprints without import side effects.
21 Oct 2025, 20:46 UTC

The problem you are actually solving
You need one Flask codebase that runs with different settings in development, testing and production, creates isolated app instances per test, and lets blueprints and extensions be reused without triggering import side effects. The useful takeaway is: use an application factory create_app(config) when those constraints exist, and keep a module-level app = Flask(__name__) only for throwaway scripts and tutorials.
Decision and constraints
The decision is factory pattern with create_app versus a single module-level Flask instance.
Constraints that push you to the factory:
- Distinct dev/test/prod configurations without import-time mutation
- Testability: a fresh app per test with its own config and extension state
- Reuse: blueprints and extensions importable without creating an app
- Multiple apps in one process, for example in-process integration tests or service variants
Options comparison
| Option | Config handling | Testability | Reuse | Complexity |
|---|---|---|---|---|
Factory create_app(config) |
Injected per call via app.config.from_object |
High, new instance per test | High, blueprints importable without app | Moderate |
Global app app = Flask(__name__) |
Import-time config, object created at import | Low, shared mutable state across tests | Low, imports trigger app creation | Low |
Trade-offs in practice
Factory adds indirection. You must create the app explicitly and push an application context when accessing context locals outside a request. That cost buys decoupling of configuration from import time.
Global app is faster to start for small scripts and tutorials. Config overrides become brittle because modules that import app see the first configuration that was applied. Extensions that expect init_app are prone to circular imports when the app is created at import time. You cannot run two differently configured apps in the same process.
Factory also makes extension initialization explicit. Create extension objects at module level and call init_app inside the factory to avoid circular imports.
What not to do
Do not store request-scoped data on the app object. Use flask.g and application context locals to prevent leakage between test instances. Do not import blueprints in a way that creates the app at import time.
Concrete factory implementation
Package layout:
myapp/
__init__.py
views.py
config.py
myapp/__init__.py
from flask import Flask
def create_app(config_obj):
app = Flask(__name__)
app.config.from_object(config_obj)
# Extensions created at module level, initialized here
# db.init_app(app)
from . import views
app.register_blueprint(views.bp)
return app
myapp/views.py
from flask import Blueprint, current_app
bp = Blueprint('main', __name__)
@bp.get('/ping')
def ping():
return {'config_name': current_app.config.get('NAME')}
myapp/config.py
class BaseConfig:
NAME = 'base'
class TestConfig(BaseConfig):
NAME = 'test'
TESTING = True
class DevConfig(BaseConfig):
NAME = 'dev'
DEBUG = True
Usage in a run script, run from project root with no special permissions:
from myapp import create_app
from myapp.config import DevConfig
app = create_app(DevConfig)
# app.run() is typical for local development
Tests use a fresh instance:
import pytest
from myapp import create_app
from myapp.config import TestConfig
@pytest.fixture
def app():
app = create_app(TestConfig)
return app
@pytest.fixture
def client(app):
return app.test_client()
Validation checks
Check that config is injected per call. Create two apps with different config objects and assert app.config values differ.
Check isolation. Run a pytest fixture that calls create_app per test and confirm test_client responses reflect the fixture config and no state leaks between tests.
Check import safety. Import blueprints before app creation to confirm no Flask app is instantiated at import time.
Limitations
Factory pattern requires careful ordering of extension init. Extensions should be created at module level but init_app called inside factory to avoid circular imports.
Async view support and context handling differ between Flask releases. Verify behavior in the Flask version you deploy.
Factory adds boilerplate for very small examples. For a single-file prototype the global app remains acceptable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.