Error Handling & Custom Pages

Reviewed & published by Brayan K

Error handling in Flask is the practice of catching HTTP errors — like a missing page or a server crash — and returning your own friendly, branded response instead of the framework's bare default message.

Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll register custom error handlers, trigger errors on purpose with abort(), and render polished 404 and 500 pages that keep users on your site instead of scaring them away.

What You'll Learn in This Lesson

1 Understanding HTTP Errors

An HTTP error is a numeric status code the server sends when something goes wrong. The browser uses it to decide what to display and how to behave.

The two you'll meet most often are 404 Not Found (the URL doesn't match any route) and 500 Internal Server Error (your code raised an unhandled exception). By default Flask shows a plain, unstyled message for these — fine for development, but jarring for real users.

2 Registering a Custom 404 Handler

The @app.errorhandler(code) decorator registers a function that Flask runs whenever that error occurs. The function should return a response and the matching status code as a tuple.

Below, the handler renders a template called 404.html and returns 404 so the browser still understands the page wasn't found. Run it and start the server with flask run to see it in action.

# app.py — a custom 404 Not Found page
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def home():
    return "Welcome! Try visiting a page that does not exist."

# This handler runs whenever Flask raises a 404 error.
# Returning (template, 404) keeps the correct status code.
@app.errorhandler(404)
def page_not_found(error):
    # In a real app this renders templates/404.html
    return render_template("404.html"), 404

# For this demo we simulate what the handler returns.
def simulate_404():
    html = "<h1>Oops! Page not found.</h1><a href='/'>Go home</a>"
    return html, 404

body, status = simulate_404()
print("Status code:", status)
print("Response body:", body)

# Run on your machine with:  flask run
# then visit any unknown URL like http://127.0.0.1:5000/nope

# ✅ Expected output:
# Status code: 404
# Response body: <h1>Oops! Page not found.</h1><a href='/'>Go home</a>

The error argument Flask passes in holds details about what went wrong. You usually don't need it for a simple page, but it's there if you want to log or display it.

3 Triggering Errors with abort()

Sometimes you decide a request should fail — for example, when a user asks for an article ID that isn't in your database. The abort(code) function stops the request immediately and raises that HTTP error, which your handler then catches.

In the example below, asking for any article other than 1 or 2 calls abort(404), which routes straight to the custom 404 handler.

# app.py — use abort() to raise a 404 on purpose
from flask import Flask, abort, render_template

app = Flask(__name__)

articles = {1: "Intro to Flask", 2: "Routing Basics"}

@app.route("/article/<int:article_id>")
def show_article(article_id):
    # If the id is not in our data, raise a 404.
    if article_id not in articles:
        abort(404)
    return f"Article: {articles[article_id]}"

@app.errorhandler(404)
def page_not_found(error):
    return render_template("404.html"), 404

# Simulate requesting an article that does not exist.
def get_article(article_id):
    if article_id not in articles:
        return "404 Not Found — no such article", 404
    return articles[article_id], 200

for test_id in (1, 99):
    body, status = get_article(test_id)
    print(f"id={test_id} -> {status}: {body}")

# Try it live with flask run, then visit /article/99

# ✅ Expected output:
# id=1 -> 200: Intro to Flask
# id=99 -> 404: 404 Not Found — no such article

🎯 Your Turn: Fill in the Blanks

Complete the app below so it registers a 500 handler and aborts when a user is missing. Replace each ___.

# 🎯 YOUR TURN: finish the error handling
from flask import Flask, abort, render_template

app = Flask(__name__)
users = {1: "Ada", 2: "Linus"}

@app.route("/user/<int:user_id>")
def show_user(user_id):
    if user_id not in users:
        ___(404)                 # raise a Not Found error
    return f"User: {users[user_id]}"

@app.errorhandler(___)           # the code for Not Found
def not_found(error):
    return render_template("404.html"), ___   # return the same code

@app.errorhandler(500)           # catch unexpected server errors
def server_error(error):
    return "Something went wrong", 500

print("Handlers registered for 404 and 500")

Common Errors & Quick Reference

❌ My custom 404 page never shows

Make sure the handler returns the status code: return render_template("404.html"), 404. Forgetting the , 404 sends a 200 and some setups skip the handler. Also confirm the function is decorated with @app.errorhandler(404).

❌ 500 handler not firing in debug mode

With debug=True, Flask shows its interactive debugger instead of your 500 page. Test custom 500 pages with debug off, and always log the real exception inside the handler.

TaskCode
Handle 404@app.errorhandler(404)
Raise an errorabort(404)
Return with statusreturn render_template("404.html"), 404
Import the toolsfrom flask import abort, render_template

🏆 Mini Challenge: A Forbidden Page

Build a small app that protects an admin route with a 403 Forbidden error.

# 🏆 Mini Challenge: protect a route with 403
from flask import Flask, abort

app = Flask(__name__)

is_admin = False  # pretend this comes from a logged-in user

@app.route("/admin")
def admin():
    if not is_admin:
        abort(403)
    return "Welcome to the admin dashboard"

@app.errorhandler(403)
def forbidden(error):
    return "🚫 You do not have permission to view this page.", 403

# Simulate the check without a running server.
def check_admin(allowed):
    if not allowed:
        return "403 Forbidden", 403
    return "Admin dashboard", 200

print(check_admin(is_admin))
# Run live with flask run, then visit /admin

❓ Frequently Asked Questions

Lesson complete — your app now fails gracefully!

You can register handlers with @app.errorhandler, raise errors on demand with abort(), and return the correct status codes so browsers and search engines stay happy.

Practice quiz

Which decorator registers a custom error handler in Flask?

  • @app.handle_error(code)
  • @app.errorhandler(code)
  • @app.on_error(code)
  • @app.catch(code)

Answer: @app.errorhandler(code). @app.errorhandler(code) runs your function when that HTTP error occurs.

What HTTP status code means 'Not Found'?

  • 200
  • 500
  • 403
  • 404

Answer: 404. 404 Not Found means the URL did not match any route or resource.

What does abort(404) do?

  • redirects to the home page
  • logs a warning only
  • stops the request and raises a 404 error
  • returns an empty 200 response

Answer: stops the request and raises a 404 error. abort(code) immediately raises that HTTP error, which a handler then renders.

What status code is an Internal Server Error?

  • 500
  • 404
  • 301
  • 204

Answer: 500. 500 Internal Server Error signals an unhandled exception on the server.

How should a 404 handler return its response?

  • return render_template('404.html')
  • return 404
  • return render_template('404.html'), 404
  • raise 404

Answer: return render_template('404.html'), 404. Return a (response, status) tuple so the 404 status code is preserved.

Which status code means 'Forbidden'?

  • 401
  • 403
  • 400
  • 418

Answer: 403. 403 Forbidden means the user is authenticated but not allowed to access it.

Why must an error handler return the correct status code?

  • so browsers, search engines, and clients see the right result
  • to make the page load faster
  • Flask requires it to compile
  • to avoid importing abort

Answer: so browsers, search engines, and clients see the right result. The status code keeps the response semantically correct (a 404 should not look like a 200).

What single argument does an error handler function receive?

  • the response
  • the request body
  • the template name
  • the error object

Answer: the error object. Flask passes the error/exception object, useful for logging the failure.

Why might a custom 500 page not show during development?

  • debug=True shows the interactive debugger instead
  • 500 cannot be handled
  • render_template is disabled
  • Flask caches the 200 page

Answer: debug=True shows the interactive debugger instead. With debug=True Flask shows its debugger; test 500 pages with debug off.

How do you raise a Forbidden error deliberately?

  • abort(403)
  • raise Forbidden
  • return 403
  • deny(403)

Answer: abort(403). abort(403) raises a 403, routed to your @app.errorhandler(403).

Continue this course

Frequently asked questions

What is an error handler in Flask?

An error handler is a function you register with @app.errorhandler(code) that Flask runs whenever a matching HTTP error occurs. Instead of showing the default plain-text error, it returns your own custom response — usually a styled HTML page.

What does abort() do?

abort(code) immediately stops the current request and raises an HTTP error with that status code, such as abort(404) for Not Found or abort(403) for Forbidden. Flask then looks for a matching error handler to render the response.

Why should I return a status code from my error handler?

The status code tells browsers, search engines, and API clients what happened. Returning ('Not Found', 404) keeps the response semantically correct so a 404 page is not mistakenly treated as a working 200 page.

Can I handle a generic 500 server error?

Yes. Register @app.errorhandler(500) to catch unexpected exceptions and show a friendly 'something went wrong' page instead of a stack trace. Always log the real error so you can debug it later.

Related lessons