The Flask Application Factory: Why Your Global App Object is Holding You Back
The global app = Flask(__name__) pattern freezes configuration at import time and leaks state between tests. Here is how the Application Factory pattern fixes it with create_app() and deferred extension binding.
30 Apr 2026, 12:38 UTC

The import-time trap
The global app = Flask(__name__) line is the first thing most Flask developers write, and the first thing that causes pain when a project grows. It builds the application at import time, freezing configuration before you have a chance to choose an environment. Tests end up sharing database connections, and running the same code with different settings becomes awkward.
The root cause is that configuration is read when Python first loads the module, not when you actually need the app. If your config class sets SQLALCHEMY_DATABASE_URI, that value is locked in the second the file is imported.
Moving app creation into a function
The Application Factory pattern replaces the global object with a function, typically named create_app(), that returns a Flask instance. Because the app is built only when you call the function, you can pass different configuration objects and get fully isolated applications.
This requires a shift in how you initialize extensions. Instead of binding them to an app immediately, you create them globally without an app and bind them later using init_app(). For example, SQLAlchemy is instantiated as db = SQLAlchemy() in a separate module, and then db.init_app(app) is called inside the factory. The extension object exists independently of any single app, so it can serve multiple instances.
A minimal factory setup
Here is a compact project layout that demonstrates the pattern. This walkthrough assumes Flask 2.x or later, where the factory pattern and CLI integration are stable.
myapp/
__init__.py
config.py
extensions.py
main.py
In config.py, define environment-specific settings:
class BaseConfig:
SECRET_KEY = "dev-secret"
class TestingConfig(BaseConfig):
TESTING = True
SECRET_KEY = "test-secret"
In extensions.py, create the extension without binding it:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
In __init__.py, build the factory:
from flask import Flask
from .extensions import db
from .config import TestingConfig
def create_app(config_object=TestingConfig):
app = Flask(__name__)
app.config.from_object(config_object)
db.init_app(app)
from .main import bp as main_bp
app.register_blueprint(main_bp)
return app
In main.py, use current_app instead of importing the app object:
from flask import Blueprint, current_app, jsonify
bp = Blueprint("main", __name__)
@bp.route("/config")
def show_config():
return jsonify(secret=current_app.config["SECRET_KEY"])
To run this from the project root, set the environment variable so Flask knows to call the factory. On Linux or macOS:
export FLASK_APP=myapp:create_app
flask run
On Windows, use set FLASK_APP=myapp:create_app instead. Flask will invoke create_app() and serve the resulting application. You should see the development server start without an import error, and visiting /config should return the secret key from whichever config class was passed to the factory.
Testing without shared state
The factory pattern shines in tests because every test can receive a brand-new application. A pytest fixture can call create_app() with a testing config, ensuring that database sessions or in-memory caches do not bleed between test cases.
import pytest
from myapp import create_app
from myapp.config import TestingConfig
@pytest.fixture
def app():
app = create_app(TestingConfig)
yield app
Because the fixture builds a fresh instance, you no longer need to reset global variables or monkey-patch configuration after import. The test client interacts with an app that has never served another request.
Trade-offs and when to skip it
The factory adds indirection. For a single-file script with one configuration and no tests, app = Flask(__name__) remains simpler and easier to read. The factory also introduces a common pitfall: circular imports. If main.py tries to import create_app from __init__.py at the top level, Python can hit a partially initialized module. The safe approach is to import blueprints inside the factory function, as shown above, or to use application context via current_app in utility modules.
There is also a learning curve. Developers must remember that app is no longer a global singleton, so direct imports like from myapp import app will fail. Every module that needs configuration or request state should use current_app instead.
A practical way to verify it works
You do not need a full test suite to confirm the pattern is correct. Open a Python shell in your project root and run:
from myapp import create_app
from myapp.config import BaseConfig, TestingConfig
app1 = create_app(BaseConfig)
app2 = create_app(TestingConfig)
print(app1.config["SECRET_KEY"]) # dev-secret
print(app2.config["SECRET_KEY"]) # test-secret
If both lines print their respective values without raising an error, your factory is producing isolated instances. That single check confirms the core benefit: configuration is decided when you call the function, not when Python first reads the file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.