Error Handling
Handle exceptions and error pages in one place to avoid leaking stack traces to users and to keep your codebase maintainable.
Registering Error Handlers
Use @app.errorhandler to register a handler for a specific HTTP status code:
from flask import render_template
@app.errorhandler(404)
def not_found(e):
return render_template("404.html"), 404
@app.errorhandler(500)
def server_error(e):
# Note: in DEBUG mode, 500s show the debugger instead of reaching here
return render_template("500.html"), 500
Handlers must return the status code explicitly (the second return value) — otherwise the response will be 200.
Raising Errors: abort
from flask import abort
@app.get("/item/<int:item_id>")
def item_detail(item_id):
item = db.session.get(Item, item_id)
if item is None:
abort(404, description="Item not found")
return render_template("item.html", item=item)
abort() raises a werkzeug.exceptions.HTTPException, which is caught by the handler for that status code; the description is available in the handler as e.description.
Custom Business Exceptions
Define exception classes carrying a status code and message so API views stay clean:
class APIError(Exception):
def __init__(self, message="Bad Request", code=400, payload=None):
super().__init__(message)
self.message = message
self.code = code
self.payload = payload or {}
@app.errorhandler(APIError)
def handle_api_error(e):
return {"error": e.message, **e.payload}, e.code
# Raise directly from views
@app.post("/orders")
def create_order():
if not request.get_json(silent=True):
raise APIError("Request body must be JSON", 400)
...
Catching All Unhandled Exceptions
Register an Exception-level handler as a safety net, but let HTTP exceptions pass through:
from werkzeug.exceptions import HTTPException
@app.errorhandler(Exception)
def handle_unexpected(e):
if isinstance(e, HTTPException):
return e # Return 404/405 etc. as-is
app.logger.exception("Unhandled exception") # Log the full stack trace
return {"error": "Internal Server Error"}, 500
Content Negotiation for Pages vs. APIs
When one app serves both pages and an API, return HTML or JSON based on the request:
from flask import request
@app.errorhandler(404)
def not_found(e):
if request.accept_mimetypes.best == "application/json" or request.path.startswith("/api/"):
return {"error": "Not Found"}, 404
return render_template("404.html"), 404
Blueprint-Level Error Handling
Blueprints can register handlers scoped to themselves:
api = Blueprint("api", __name__, url_prefix="/api")
@api.errorhandler(ValueError)
def handle_value_error(e):
return {"error": str(e)}, 400
Note: errors like 404/405 are raised by the routing system before a blueprint is entered, so only application-level handlers catch them — a blueprint-level errorhandler(404) never fires.
Working with Logging
- Always log 500-level errors with
app.logger.exception() to capture the full stack trace (see the Logging chapter).
- 4xx errors are usually client problems; log them as
warning or not at all to avoid log noise.