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.