Laravel create custom exception with parameters & return as JSON

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Custom Error Handling in Laravel: Throwing Exceptions that Return JSON

As developers building robust APIs with Laravel, we often encounter situations where standard HTTP error responses are insufficient. We need more granular control over what happens when an expected condition fails—specifically, throwing a custom exception and ensuring that the resulting response is cleanly formatted as JSON instead of a generic HTML error page.

The challenge you've encountered is common: simply throwing a PHP exception doesn't automatically translate into the desired JSON response format in a Laravel application unless specific handlers are configured. Today, we will dive deep into how to create custom exceptions that automatically render themselves as structured JSON responses, giving you complete control over your API error handling.

Why Custom Exceptions Matter for APIs

When building an API, consistency is paramount. Instead of returning a raw HTTP 500 error or a generic message, clients expect predictable structures. By creating custom exception classes, we encapsulate the error context (like success status, specific messages, and data) directly into the exception object itself. This allows us to hijack the standard exception handling mechanism in Laravel and force it to output exactly the JSON structure our API expects.

This practice aligns perfectly with building clean, maintainable services, a core principle advocated by frameworks like Laravel, which emphasizes elegant solutions for complex tasks.

Step 1: Designing the Custom Exception Class

To make an exception render as JSON, we need to override the standard behavior. In Laravel, exceptions are usually rendered via the render() method defined within the exception class itself.

Here is how you structure a custom exception, ensuring it contains all the necessary data for a clean API response:

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Response;

class NullException extends Exception
{
    public $isSuccess;
    public $message;
    public $code;
    public $data;

    /**
     * Constructor to capture all necessary error details.
     */
    public function __construct($isSuccess, $message, $code, $data = null, Exception $previous = NULL)
    {
        $this->isSuccess = $isSuccess;
        $this->message = $message;
        $this->code = $code;
        $this->data = $data;
        // Call the parent constructor
        parent::__construct($message, $code, $previous);
    }

    /**
     * Override the render method to return a JSON response.
     */
    public function render()
    {
        return response()->json([
            'success' => $this->isSuccess,
            'message' => $this->message,
            'data' => $this->data,
        ], $this->code);
    }
}

The crucial part here is the render() method. By implementing this method, whenever Laravel catches an instance of NullException, it knows exactly how to format the output—as a structured JSON response with appropriate HTTP status codes defined by $this->code.

Step 2: Throwing and Catching in Your Controller

Now, let's see how this integrates into your controller logic. Instead of relying solely on the standard try...catch block within the controller method to return the response directly (which often complicates error flow), we throw our custom exception and let Laravel’s exception handler manage the output.

use App\Exceptions\NullException;
use Illuminate\Http\Request;

class AttendanceController extends Controller
{
    public function checkApiKey(Request $request)
    {
        try {
            // Simulate checking an API key
            $check_api_key = $this->attendance_libraries->check_api_key($request);

            if ($check_api_key === null) {
                // Throw the custom exception with all necessary data
                throw new NullException(false, 'API Key Not Found', 401, null);
            }

            return response()->json(['status' => 'success', 'key' => $check_api_key]);

        } catch (NullException $e) {
            // If the exception is caught here, we can simply let Laravel handle it,
            // OR re-throw it to be handled by global exception handling.
            // For clean API flow, letting it bubble up is often better if you have
            // a centralized handler set up.
            throw $e; 
        } catch (\Exception $e) {
            // Handle other unexpected errors
            return response()->json(['success' => false, 'message' => 'An unexpected error occurred'], 500);
        }
    }
}

Conclusion: The Power of Structured Error Handling

By implementing custom exception classes with a dedicated render() method, you move beyond simple error logging and establish a predictable API contract. This approach transforms an internal PHP error into a standardized, client-friendly JSON response. When developing sophisticated services on Laravel, mastering this level of abstraction is key to creating resilient and professional APIs. Focus on defining your application's boundaries clearly, and custom exceptions are the perfect tool for achieving that clarity.