How to Return JSON Errors in Laravel APIs Instead of Rendered Web Page Errors

Building a Laravel app that has both a traditional web front-end and a modern API? Then you’ve probably stumbled across this annoying issue: when an API call fails, Laravel returns a full HTML error page, not the JSON your frontend app expects.
In this short guide, I’ll show you how to handle that properly using Laravel’s built-in exception configuration, so that web requests still get pretty error pages, and API calls get clean JSON errors with the correct status code.
The Problem: Shared Exception Handling for Web and API
Let’s say you’re building a blog platform. You have two routes to fetch a blog post by slug or ID:
Web version:
// app/Http/Controllers/PostController.php
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
API version:
// app/Http/Controllers/API/V1/PostController.php
public function show(Post $post)
{
return $post;
}
Thanks to route model binding, Laravel will automatically resolve the Post
model from the route. But if the post doesn’t exist, Laravel will throw a ModelNotFoundException
, which becomes a NotFoundHttpException
.
That’s fine, except the API route will return an HTML error page instead of JSON.
That’s not what your app wants. It expects a 404
with a proper JSON body.
The Goal
We want Laravel to behave like this:
-
Web routes → return full HTML error pages (default Laravel behavior)
-
API routes → return clean JSON error messages (we’ll fix that)
The Fix: Smart Exception Rendering with Laravel 11+
Starting with Laravel 11, exception handling is more elegant and configurable.
To fix this issue, open your bootstrap/app.php
file and update the exception configuration like so:
use Illuminate\Http\Request;
use Illuminate\Foundation\Configuration\Exceptions;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function ($middleware) {
// Register middleware if needed
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(
fn(Request $request) => $request->is('api/*') || $request->wantsJson()
);
})
->create();
This single line:
$request->is('api/*') || $request->wantsJson()
tells Laravel: “Hey, return JSON responses when handling API requests or when the client says it prefers JSON.”
No more custom renderable callbacks. Laravel does the right thing based on the request context.
Try It Out
Make an API call for a post that doesn’t exist:
GET /api/v1/posts/999999
Accept: application/json
Before:
An ugly HTML error page 🥴
After:
{
"message": "Not Found"
}
Bonus: Don't Forget the Accept
Header
If you're testing this in Postman or via frontend code, always make sure to send:
Accept: application/json
Laravel uses this to decide whether you want a JSON response, even outside the /api/*
route group.
Conclusion
Laravel makes it easy to support both web and API clients, but only if you help it understand how to respond.
With this simple shouldRenderJsonWhen()
configuration, you get the best of both worlds:
-
Web users see beautiful error pages.
-
API clients get predictable JSON responses.
Clean, reliable, and built-in, no extra packages required.
Keep building great things! đź’Ş