The Flask request context is an invisible cloak that wraps your application’s request handling, ensuring that each incoming HTTP request gets its own isolated bubble of data.

Let’s see this in action. Imagine a simple Flask app that needs to track the number of times a specific endpoint has been hit within a single request.

from flask import Flask, request, g

app = Flask(__name__)

@app.before_request
def before_request_func():
    g.request_counter = 0

@app.route('/')
def index():
    g.request_counter += 1
    return f"Request counter: {g.request_counter}"

@app.route('/another')
def another():
    g.request_counter += 1
    return f"Request counter in another endpoint: {g.request_counter}"

if __name__ == '__main__':
    app.run(debug=True)

When you hit / in your browser, you’ll see "Request counter: 1". If you then hit /another in the same browser tab (which makes a new HTTP request), you’ll see "Request counter in another endpoint: 1". The counter resets for each request. This is the magic of the request context.

Flask uses a mechanism called "context locals" to achieve this per-request isolation. The most prominent of these is flask.g. You can think of g as a special dictionary that is unique to each active request. Anything you attach to g – like g.request_counter = 0 – is only visible and accessible within the scope of that specific request. Once the request is finished and a response is sent, the g object for that request is discarded.

This system solves a fundamental problem in web applications: how to manage state that is specific to a single user interaction without interfering with other users’ interactions. Without request contexts, you’d be constantly fighting race conditions or needing to pass request-specific data through every single function call, which would quickly become unmanageable.

The request context is managed by two key objects: request and session. The request object (available as flask.request) holds all the incoming HTTP request data: headers, arguments, form data, JSON payload, etc. The session object (available as flask.session) is similar to g but is designed for data that needs to persist across multiple requests from the same client, typically using cookies.

Behind the scenes, Flask uses Python’s contextvars (or threading.local in older versions) to implement these context locals. When a request comes in, Flask pushes a new context onto the stack. This context includes the request object, the session object, and any other context locals like g. When the request is done, the context is popped. This stack-based approach allows for nested operations or even asynchronous request handling to maintain distinct contexts.

The before_request decorator is your entry point for setting up per-request state. You can define functions decorated with @app.before_request that will execute before each request handler. This is the perfect place to initialize things that you know you’ll need for the duration of the request, like setting up database connections, user authentication state, or, as in our example, initializing our g.request_counter.

It’s worth noting that while g is excellent for temporary, request-scoped data, you should avoid storing large amounts of data there, as it’s all kept in memory for the duration of the request. For data that needs to persist across requests, the session object is the appropriate tool.

When you’re debugging, you’ll often see RuntimeError: Working outside of request context. This means you’re trying to access a context-local object like request, session, or g from a place that isn’t associated with an active HTTP request. This could happen if you try to access request.args in a background thread or a scheduled task that wasn’t initiated by a Flask request.

The next logical step after understanding how to manage per-request state is to learn how to make that state persist across multiple requests from the same user.

Want structured learning?

Take the full Flask course →