Custom exceptions in Laravel: how to create, manage, and apply them to your business logic

Video thumbnail

We already know how to create custom helpers. Next on the list is creating a custom exception in Laravel; which might seem advanced at first, but is actually one of those practices that makes the difference between messy code and a professional, maintainable project. In my experience, mastering this topic allows you to control in detail what happens when something fails in your application, without relying on generic messages or improvised solutions.

What are Custom Exceptions in Laravel?

In Laravel, exceptions are the way the framework communicates that something went wrong during execution. A custom exception is simply a class that you define to represent a specific error within your application.

Difference between native and custom exceptions

Laravel comes with many default exceptions (for example, ModelNotFoundException or ValidationException), but when your business logic has particular cases—such as an invalid order or a nonexistent resource—it is ideal to create your own classes.

Why use them in real projects

In my projects, custom exceptions have allowed me to separate errors from the logical flow, facilitating both maintenance and debugging. You don't just throw an error, but you give it a semantic meaning (“user not found”, “invalid order”) that you can handle differently depending on the context.

How to create a custom exception step by step

Basic structure of an exception class

Create a file in app/Exceptions with the name that describes the problem. For example:

app/Exceptions/ResourceNotFoundException.php

namespace App\Exceptions;
use Exception;
class ResourceNotFoundException extends Exception
{
 // custom properties or methods...
}

Example: ResourceNotFoundException

And to use it, we use throw to launch the exception:

use App\Models\User;
use App\Exceptions\ResourceNotFoundException;
public function getUserById($id)
{
 $user = User::find($id);
 if (!$user) {
 throw new ResourceNotFoundException("User with ID $id not found.");
 }
 return $user;
}

When and how to throw the exception (throw)

The throw is used to interrupt the code flow and send the exception to Laravel's global handler.
This is useful for avoiding repeating checks in every part of the system. Personally, it has helped me keep my controllers clean and coherent.

Handling custom exceptions in Laravel

Using the Handler (app/Exceptions/Handler.php)

The handler is the heart of Laravel's error system. Here you decide what to do with each type of exception.

public function register()
{
   $this->renderable(function (ResourceNotFoundException $e, $request) {
       if ($request->is('api/*')) {
           return response()->json(['message' => 'Recurso no encontrado'], 404);
       }
       return response()->view('errors.404');
   });
}

This way you achieve a controlled response for both APIs and views.

Customizing report() and render()

If you prefer, you can directly override the report() and render() methods within your custom exception:

public function report()
{
   // logs send msj...
}
public function render($request)
{
   return response()->json(['error' => $this->getMessage()], 404);
}

On one occasion, for example, I configured report() to send an email to the support team every time a critical order failed. It was a simple way to stay aware of errors without relying only on logs.

JSON responses and custom views

If your application combines frontend and API, you can return different responses depending on the request type. In my tests, this avoids confusing errors to the client and improves the overall user experience.

Practical use cases

Error control in APIs (404, 500, etc.)

In modern APIs, returning well-structured JSON is key. A custom exception allows you to do this easily, ensuring consistent responses:

return response()->json([
   'error' => 'User not found',
   'code' => 404
], 404);

Automatic notification or log sending

When I worked on an order system, I configured exceptions to send a notification to the sales team if a payment failed. I achieved this with the report() method and a Laravel notification:

public function report()
{
   Notification::route('mail', 'soporte@miempresa.com')
       ->notify(new PaymentFailedNotification($this));
}

Best practices for robust error handling

  • Create exceptions with clear and specific names.
  • Centralize their handling in the Handler.
  • Avoid catching generic exceptions excessively.
  • Use report() for monitoring and render() for the response to the user.

Common errors and how to avoid them

Uncaught exceptions

If you throw an exception and don't handle it anywhere, Laravel will display it raw. Always define a response or a custom render.

Incorrect API responses

Avoid returning HTML on API endpoints. Always use structured JSON, even for errors.

Badly typed or out-of-context exceptions

Don't create generic exceptions for everything. Use one for each important business case (e.g., InvalidOrderException, PaymentFailedException).

Conclusion and additional resources

Custom exceptions are a powerful tool for organizing your code and improving the user experience.
Since I started using them systematically, my Laravel projects are much more maintainable and easier to debug.
Remember: a well-designed exception not only communicates an error, it also expresses intent and context.

FAQ

What is the difference between report() and render()?
report() is used to log or notify the error; render() to return the response to the client.

How to return JSON when an exception occurs?
You can handle it in the Handler or within the exception itself using render().

Can I register multiple custom exceptions?
Yes. In fact, it is recommended to have one for each important business case to maintain clarity and control.

The next operation is to learn the use of Localizations and Translations in Laravel.

I agree to receive announcements of interest about this Blog.

We will learn how to create custom exceptions in Laravel, their use, examples, and why you should use them.

| 👤 Andrés Cruz

🇪🇸 En español