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
- What HTTP error codes like 404 and 500 mean
- How to register handlers with @app.errorhandler
- How to trigger errors deliberately with abort()
- How to render custom error pages with render_template
- Why returning the right status code matters
- How to catch unexpected 500 server errors gracefully
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.
- 404 Not Found: the requested page or resource doesn't exist
- 403 Forbidden: the user isn't allowed to access this resource
- 400 Bad Request: the request was malformed or missing data
- 500 Internal Server Error: something broke on the server side
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.
| Task | Code |
|---|---|
| Handle 404 | @app.errorhandler(404) |
| Raise an error | abort(404) |
| Return with status | return render_template("404.html"), 404 |
| Import the tools | from flask import abort, render_template |
🏆 Mini Challenge: A Forbidden Page
Build a small app that protects an admin route with a 403 Forbidden error.
- Add a route at /admin that checks a fake is_admin flag
- Call abort(403) when the user is not an admin
- Register an @app.errorhandler(403) that returns a custom message and the 403 code
# 🏆 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
- Previous: Database Migrations
- Next: Flashing Messages — Show one-time notifications like 'Saved!' after an action
- Quick reference: Flask cheat sheet › Request & Response
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.