Flask init_app inside application factory: detection and resolution

The endpoint returned an empty JSON and the log printed a DataFrame with unexpected columns. The Flask app started, yet the custom extension never appeared in app.extensions. Only after dumping the extensions dictionary did we realize the init_app call was misplaced.

Here’s what this looks like:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pandas as pd

# extension instantiated at module level
db = SQLAlchemy()

# premature init_app call – this is the bug
# spent 30min debugging this
# FIXME: temporary until we refactor the factory
# db.init_app(app)  # app does not exist yet


def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
    # correct place for init_app
    db.init_app(app)
    @app.route('/')
    def index():
        # simulate processing a DataFrame
        df = pd.DataFrame({'a': [1, 2]})
        return df.to_json()
    return app

app = create_app()
print('extensions registered:', app.extensions)
# Output shows {} – the SQLAlchemy extension is missing

Diagnosing the issue:

# Run this after creating the app
print('Registered extensions:', app.extensions)
# If the dict is empty or missing your extension key, the init_app call was misplaced.

The root cause is calling init_app before a Flask app instance exists. Developers often assume a module‑level extension can be initialized early, but init_app needs the concrete app to attach its state. The Flask docs state that extensions must be bound inside the factory after the app is created.

How to fix it

Quick Fix: move the db.init_app(app) line inside the create_app function, after the Flask object is created.

def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
    db.init_app(app)  # now the extension registers correctly
    return app

The gotcha is that the extension object itself can be created early, but binding must happen later. For production‑ready code, keep the extension uninitialized at import time and register it explicitly in every factory you use. This makes the extension’s lifecycle clear and avoids silent missing‑extension bugs.

# extensions.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()  # no init_app here

# app_factory.py
from flask import Flask
from .extensions import db

def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
    db.init_app(app)  # explicit registration
    return app

By deferring the call, the app’s configuration is already in place, and the extension can access it without raising cryptic missing‑registration symptoms.