TL;DR
You're calling current_app, g, or another Flask context-local from somewhere Flask didn't set up for you β a background thread, a script, a Celery task. Push the context yourself:
with app.app_context():
# your code here
result = db.session.query(User).all()
Threads, Celery tasks, and scheduled jobs each need a slightly different approach. Jump to whichever section matches your situation.
The full error
RuntimeError: Working outside of application context. This typically means that you attempted to use functionality that needed to interface with the current application object in some way.
Flask often prints a second line identifying the exact object β current_app, g, url_for(), or a Flask-SQLAlchemy model. That second line is where to start debugging.
Root cause
Under the hood, Flask keeps a context-local stack β one per thread β holding the current app and current request. Every incoming HTTP request pushes two things onto that stack: an application context and a request context. Your view functions get both for free.
Step outside that request lifecycle, though, and the stack is empty. Common culprits:
- A background thread spawned with
threading.Thread - A Celery or RQ worker task
- A standalone Python script that imports your models
- A
pytesttest with no app context fixture - An APScheduler or Flask-APScheduler scheduled job
- A Flask CLI command run as a plain Python script instead of via
flask <command>
None of these go through Flask's request lifecycle, so there's nothing on the stack.
Fix 1 β Wrap the call in app.app_context() (most common)
Got a direct reference to the app object? A context manager solves it in two lines:
from myapp import create_app
app = create_app()
with app.app_context():
from myapp.models import User
users = User.query.all()
print(users)
Standalone scripts, management tasks, one-off data migrations β this pattern covers all of them.
Fix 2 β Background threads
Context locals don't cross thread boundaries. Each new thread starts with an empty stack, so you need to push a fresh context inside the thread function:
import threading
from flask import current_app
def background_job(app):
with app.app_context():
# current_app works here
print(current_app.config["DATABASE_URI"])
# Pass the real app object β not current_app
thread = threading.Thread(target=background_job, args=(app,))
thread.start()
Always pass app, not current_app. current_app is a proxy that only resolves inside an active context. Pass it to another thread and it explodes immediately.
Fix 3 β Celery tasks
Celery workers are separate processes with no Flask context at all. The cleanest fix is a custom Task base class that wraps every task call in an app context:
# celery_app.py
from celery import Task
from myapp import create_app
class FlaskTask(Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return super().__call__(*args, **kwargs)
app = create_app()
celery = app.extensions["celery"]
celery.Task = FlaskTask
Flask 2.3+ includes a built-in Celery integration. Check the official Flask + Celery tutorial for the init_celery(app) factory pattern β it handles this wiring for you.
Fix 4 β pytest tests
Push the context via a fixture and yield from inside it. Every test that receives the fixture runs inside a live app context:
# conftest.py
import pytest
from myapp import create_app, db as _db
@pytest.fixture()
def app():
app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:"})
with app.app_context():
_db.create_all()
yield app
_db.drop_all()
@pytest.fixture()
def client(app):
return app.test_client()
The yield inside the context block is the key detail β setup, test body, and teardown all run within the same active context.
Fix 5 β APScheduler / Flask-APScheduler jobs
Scheduled jobs fire outside the request cycle. With Flask-APScheduler, reach the app via scheduler.app and push a fresh context inside the job function:
from flask_apscheduler import APScheduler
scheduler = APScheduler()
@scheduler.task("cron", id="cleanup", hour=2)
def cleanup_job():
with scheduler.app.app_context():
from myapp.models import Session
Session.query.filter(Session.expired == True).delete()
db.session.commit()
Fix 6 β Flask CLI commands
Commands registered with @app.cli.command get an app context pushed automatically when you run them through flask <command>. No extra setup needed.
The trap: running the file directly with python seed_db.py skips the Flask CLI entirely, so no context is pushed. Always use the CLI entry point:
@app.cli.command("seed-db")
def seed_db():
# app context is already active β no extra push needed
db.session.add(AdminUser(email="admin@example.com"))
db.session.commit()
click.echo("Done.")
Run it with flask seed-db, not python seed_db.py.
Verification
Once you've applied a fix, re-run the failing code path. The RuntimeError should be gone. Add a quick sanity check inside the context block to confirm:
with app.app_context():
from flask import current_app
assert current_app._get_current_object() is app
print("App context OK:", current_app.name)
For Flask-SQLAlchemy, confirm the session is alive too:
with app.app_context():
from myapp.models import User
count = User.query.count()
print(f"{count} users in DB β context is live")
Quick reference
ScenarioFix
Standalone script`with app.app_context():`
Background threadPass `app` to thread, push inside
Celery taskCustom `Task` base class or Flask 2.3+ helper
pytestApp fixture with `with app.app_context(): yield`
APScheduler job`with scheduler.app.app_context():`
Flask CLI commandUse `flask <command>` β context is automatic

