Laravel

Error Handling

Laravel provides robust error and exception handling out of the box. You can customize how exceptions are reported and rendered to deliver friendly error pages and proper logging.

Global Exception Handler (Laravel 11+): In Laravel 11+, exceptions are configured directly in bootstrap/app.php using the withExceptions() method instead of a dedicated Kernel class.
📄bootstrap/app.php
PHP
ERROR: syntax error, unexpected identifier "t"
Custom Exception Class: Create domain-specific exceptions to encapsulate error context and provide clean handling at a higher level.
📄app/Exceptions/PaymentFailedException.php
PHP
<?php

namespace App\Exceptions;

use Exception;

class PaymentFailedException extends Exception
{
    protected string $gatewayCode;

    public function __construct(string $message, string $gatewayCode = '', int $code = 402)
    {
        parent::__construct($message, $code);
        $this->gatewayCode = $gatewayCode;
    }

    public function getGatewayCode(): string
    {
        return $this->gatewayCode;
    }

    // Optionally, render the exception directly from the class
    public function render(\Illuminate\Http\Request $request): \Illuminate\Http\Response
    {
        return response()->view('errors.payment-failed', [
            'message'     => $this->getMessage(),
            'gatewayCode' => $this->gatewayCode,
        ], 402);
    }

    // Optionally, control reporting from the class itself
    public function report(): bool
    {
        // Return false to skip default reporting
        return $this->gatewayCode !== 'CARD_DECLINED';
    }
}

// --- Throwing the exception in a service ---
// throw new PaymentFailedException('Your card was declined.', 'CARD_DECLINED');
The abort() Helper: Use abort() to immediately stop execution and return an HTTP error response. Laravel will render the corresponding error view.
📄app/Http/Controllers/PostController.php
PHP
<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function show(int $id)
    {
        $post = Post::find($id);

        // Throw a 404 Not Found response
        if (!$post) {
            abort(404, 'Post not found.');
        }

        // Throw a 403 Forbidden response
        if (!auth()->user()->can('view', $post)) {
            abort(403, 'You do not have permission to view this post.');
        }

        // Throw a 401 Unauthorized response
        if (!auth()->check()) {
            abort(401, 'Please log in to view this content.');
        }

        return view('posts.show', ['post' => $post]);
    }

    public function destroy(int $id)
    {
        $post = Post::findOrFail($id); // Automatically aborts with 404 if not found

        $post->delete();

        return redirect()->route('posts.index')->with('success', 'Post deleted.');
    }
}
Custom Error Blade Views: Place Blade files in resources/views/errors/ named after the HTTP status code (e.g., 404.blade.php, 500.blade.php) to render custom error pages.
📄resources/views/errors/404.blade.php
HTML
@extends('layouts.app')

@section('title', 'Page Not Found')

@section('content')
<div class="error-page text-center py-5">
    <h1 class="display-1 fw-bold text-danger">404</h1>
    <h2 class="mb-3">Oops! Page Not Found</h2>
    <p class="text-muted mb-4">
        {{ $exception->getMessage() ?: 'The page you are looking for could not be found.' }}
    </p>
    <a href="{{ url('/') }}" class="btn btn-primary">
        &larr; Back to Home
    </a>
</div>
@endsection
📄resources/views/errors/500.blade.php
HTML
@extends('layouts.app')

@section('title', 'Server Error')

@section('content')
<div class="error-page text-center py-5">
    <h1 class="display-1 fw-bold text-warning">500</h1>
    <h2 class="mb-3">Internal Server Error</h2>
    <p class="text-muted mb-4">
        Something went wrong on our end. Our team has been notified.
    </p>
    <a href="{{ url('/') }}" class="btn btn-outline-secondary">
        &larr; Return Home
    </a>
</div>
@endsection
Publish Default Error Views: Run the Artisan command to publish the default error views so you can customize them.
📄Terminal
BASH
# Publish default error views to resources/views/errors/
php artisan vendor:publish --tag=laravel-errors

# Files published:
# resources/views/errors/401.blade.php
# resources/views/errors/403.blade.php
# resources/views/errors/404.blade.php
# resources/views/errors/419.blade.php
# resources/views/errors/429.blade.php
# resources/views/errors/500.blade.php
# resources/views/errors/503.blade.php
Logging with the Log Facade: Laravel wraps Monolog to provide powerful logging. Use different channels and severity levels to record application events.
📄app/Services/OrderService.php
PHP
<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;
use App\Models\Order;
use Exception;

class OrderService
{
    public function processOrder(int $orderId): void
    {
        try {
            $order = Order::findOrFail($orderId);

            // Log at different severity levels
            Log::debug('Processing order', ['order_id' => $orderId]);
            Log::info('Order found', ['user_id' => $order->user_id]);

            // Perform processing...
            $this->chargePayment($order);

            Log::info('Order processed successfully', ['order_id' => $orderId]);

        } catch (Exception $e) {
            // Log with context data
            Log::error('Order processing failed', [
                'order_id'  => $orderId,
                'exception' => $e->getMessage(),
                'trace'     => $e->getTraceAsString(),
            ]);

            // Log to a specific channel (e.g., Slack, daily file)
            Log::channel('slack')->critical('CRITICAL: Order #' . $orderId . ' failed!');

            throw $e;
        }
    }
}

// --- Log levels (RFC 5424) ---
// Log::emergency($message, $context);
// Log::alert($message, $context);
// Log::critical($message, $context);
// Log::error($message, $context);
// Log::warning($message, $context);
// Log::notice($message, $context);
// Log::info($message, $context);
// Log::debug($message, $context);