Request Hooks (before/after_request)

A request hook is a function Flask runs automatically before or after every request, letting you add shared logic like authentication checks, timing, or response headers in one place.

Learn Request Hooks (before/after_request) in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…

Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll register before_request and after_request hooks, short-circuit requests for authorization, add headers to every response, run guaranteed cleanup with teardown_request , and learn the modern replacement for the removed before_first_request .

Flask gives you two decorators that wrap every request. A function decorated with @app.before_request runs before the matching view, which is the perfect spot for shared setup like an authentication check or opening a database connection. A function decorated with @app.after_request runs after the view has produced a response, so it is where you tweak the outgoing response — for example, adding a header to every page.

There is one rule you must never break: after_request must return the response object . Flask hands you the response, expects you to hand it back (modified or not), and uses your return value as what the client actually receives. Forget the return and Flask raises an error.

In the example below, the before hook prints a message during the request and the after hook stamps an X-App header onto every response. Because the before hook prints while the request is being handled, the captured output appears first, followed by the response body and then the header value we inspect afterwards.

The before hook fires first, so you see before_request ran , then the view's Hello , then the Flask-Demo header that after_request attached on the way out.

A before_request hook has a superpower: if it returns a value , Flask treats that value as the response and skips the view entirely. This is exactly how you implement an authorization guard — check the incoming request, and if it fails, return an error response so the protected view never runs.

The hook below protects anything under /admin . It inspects request.path and request.args ; if the secret key is missing, it returns "Forbidden", 403 and the admin view is never called. Public routes and correctly-keyed admin requests fall straight through because the hook returns None (an implicit return), which tells Flask to continue as normal.

The public page returns 200 , the unkeyed admin request is blocked with 403 , and the correctly-keyed request reaches the view and returns Admin panel — all decided in one central hook.

A classic hook pairing is request timing. A before_request hook records a start time on the request-scoped g object, and an after_request hook reads it back to measure how long the request took. Because g lives for exactly one request, the two hooks can safely stash and retrieve data without clashing across concurrent requests.

For cleanup that must always happen, use @app.teardown_request . It runs at the very end of the request — even if the view raised an exception — and it receives that exception (or None ) as its argument. This makes it the right place to close a database connection or release a resource you opened earlier.

Note that before_first_request was removed in Flask 2.3 . Instead of running one-time setup on whichever request happens to arrive first, do it explicitly at startup — for example inside an application factory, or in a with app.app_context(): block right after you create the app. That is more predictable and testable.

The view returns done , and because the before hook stored a timestamp the after hook could measure, the X-Timed header reads yes . The teardown_request hook runs silently at the end of every request.

Remember: after_request always returns the response; teardown_request receives the exception (or None ), not the response.

Complete the hooks below. Register a before_request and an after_request that adds an X-Lesson header. Replace each ___ — and don't forget that after_request must return the response.

If your after_request function forgets to return response , it implicitly returns None , and Flask raises an error because it expects the response object back. Always end an after_request hook with return response .

before_first_request was removed in Flask 2.3+. Run one-time setup at startup instead — in an application factory or a with app.app_context(): block — rather than tying it to the first request.

Lesson complete — you can wrap every request!

You can now run shared logic with before_request , modify and return the response in after_request , short-circuit a request to enforce authorization, guarantee cleanup with teardown_request , and run one-time setup at startup instead of the removed before_first_request .

🚀 Up next: Class-Based Views — organize related routes with reusable view classes.

Practice quiz

When does a function decorated with @app.before_request run?

  • After the response is sent
  • Before the matching view function
  • Only on the first request
  • Only on errors

Answer: Before the matching view function. before_request hooks run before the view for every request.

What must an @app.after_request function do with the response?

  • Log it
  • Delete it
  • Return it
  • Cache it

Answer: Return it. after_request receives the response and must return it (modified or not).

What happens if a before_request hook returns a value?

  • Flask raises an error
  • The value is ignored
  • It is added as a header
  • Flask uses it as the response and skips the view

Answer: Flask uses it as the response and skips the view. Returning from before_request short-circuits the request, skipping the view.

Which hook is ideal for an authorization guard that blocks a request?

  • @app.before_request
  • @app.after_request
  • @app.teardown_request
  • @app.errorhandler

Answer: @app.before_request. before_request can inspect the request and return early to block it.

What does an @app.teardown_request function receive as its argument?

  • The request body
  • The exception (or None)
  • The response object
  • The status code

Answer: The exception (or None). teardown_request receives the exception raised during the request, or None.

Which hook always runs at the end, even if the view raised an exception?

  • before_request
  • after_request
  • teardown_request
  • before_first_request

Answer: teardown_request. teardown_request runs at the very end regardless of exceptions — ideal for cleanup.

Where is a good place to stash a per-request start time for timing?

  • A module global
  • The session
  • The response headers
  • The g object

Answer: The g object. g is request-scoped, so before/after hooks can stash and read data safely.

What error does forgetting in after_request cause?

  • Flask errors because None is not a valid response
  • A 404
  • Nothing, it is optional
  • A redirect loop

Answer: Flask errors because None is not a valid response. Without a return, the function yields None, which Flask cannot use as a response.

What replaced before_first_request, removed in Flask 2.3?

  • @app.first_request
  • One-time setup at startup (e.g. app factory / app_context)
  • @app.before_request only
  • It cannot be replaced

Answer: One-time setup at startup (e.g. app factory / app_context). Run one-time setup explicitly at startup rather than on the first request.

Why is the g object safe for two hooks across concurrent requests?

  • It is encrypted
  • It uses a global lock
  • It lives for exactly one request
  • It is read-only

Answer: It lives for exactly one request. g is scoped to a single request, so concurrent requests don't clash.