Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Api Problem Laravel Package

pedrosalpr/laravel-api-problem

Laravel API Problem brings RFC 7807/9457 problem details to your Laravel API, helping you return consistent, standards-compliant JSON error responses. Document, debug, and consume API errors more easily with structured problem payloads and simple helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and discover the surface area

    composer require pedrosalpr/laravel-api-problem
    

    Because the package is small (11 stars), do a 30‑second recon before wiring it in:

    • vendor/pedrosalpr/laravel-api-problem/README.md — the canonical method names.
    • vendor/pedrosalpr/laravel-api-problem/src/ — the Problem value object / response class and the service provider. This tells you whether it exposes a builder, a Responsable, a response macro, or exception rendering hooks.
    • vendor/pedrosalpr/laravel-api-problem/config/ — if a config file ships, publish it:
      php artisan vendor:publish --tag=laravel-api-problem-config
      
    • composer.json in the package — confirm the PSR‑4 namespace (likely Pedrosalpr\LaravelApiProblem\...) before writing imports.
  2. Where to look first in your app

    • bootstrap/app.php (Laravel 11+) or app/Exceptions/Handler.php (Laravel 10 and below) — this is where you decide which exceptions become problem responses.
    • Your API route group — you only want problem+json on /api/*, not on Inertia/Blade routes.
    • Any existing abort(404), ValidationException, or custom exception classes — those are the first things to normalise.
  3. First use case: replace the default error envelope The lowest-friction win is making a 404 or validation failure return RFC 9457 fields instead of Laravel's { "message": ..., "errors": ... } shape. Pick one endpoint you already test, have it throw a validation error, and assert the response is application/problem+json with type, title, status, detail, and instance. Once that's green, roll the pattern out through the exception handler (see next section) rather than editing controllers one by one.

Implementation Patterns

Return a problem directly from a controller. The typical shape is a Problem object you build and return (implementing Responsable or converted to a response). The standard members map 1:1 to RFC 9457:

use Pedrosalpr\LaravelApiProblem\Problem; // confirm namespace in README

return Problem::make()
    ->type('https://example.com/probs/out-of-credit')
    ->title('You do not have enough credit.')
    ->status(403)
    ->detail('Your current balance is 30, but that costs 50.')
    ->instance('/account/12345/msgs/abc');

Do this for genuinely exceptional, per-endpoint cases (payment required, quota exceeded). For everything else, prefer the global handler so the pattern is consistent.

Centralise in the exception renderer. This is the pattern that pays off day‑to‑day. In bootstrap/app.php:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (Throwable $e, Request $request) {
        if (! $request->is('api/*') && ! $request->expectsJson()) {
            return null; // fall through to default handling
        }

        // Map known exception types to RFC 9457 problems
        if ($e instanceof ValidationException) {
            return Problem::make()
                ->status(422)
                ->title('Validation Failed')
                ->detail('The given data was invalid.')
                ->withExtension('errors', $e->errors());
        }

        if ($e instanceof ModelNotFoundException) {
            return Problem::make()
                ->status(404)
                ->title('Resource Not Found');
        }
        // ...
    });
});

Wrapping this in a small ProblemMapper service (or a dedicated ApiExceptionRenderer) keeps bootstrap/app.php readable and makes the mapping unit‑testable.

Encode domain errors as first‑class exceptions. Define OutOfCreditException, RateLimitedException, etc., each carrying a problemType() and a problemTitle(). The renderer then does one instanceof check or, better, an interface like ConvertsToProblem with a toProblem() method. This keeps controllers free of response‑building and keeps the problem taxonomy in one place.

Use extension members for machine-readable context. RFC 9457 explicitly allows extra members. errors for 422, balance/costs for 403, retry_after for 429. Clients can then branch on type and read structured data without scraping detail. Keep extension keys from colliding with the five standard members — title, type, status, detail, instance are reserved.

Force negotiation with middleware. If clients send Accept: */* or omit it, Laravel's expectsJson() may not fire. A tiny middleware on the API group can set Accept: application/problem+json (or at least application/json) before the request hits the handler:

public function handle(Request $request, Closure $next)
{
    $request->headers->set('Accept', 'application/problem+json, application/json');
    return $next($request);
}

This is especially useful for curl-based smoke tests and mobile clients that send weak Accept headers.

Testing pattern. Assert the header and the shape, not just the status:

$response = $this->getJson('/api/orders/999');

$response->assertStatus(404)
    ->assertHeader('Content-Type', 'application/problem+json')
    ->assertJsonStructure(['type', 'title', 'status', 'detail', 'instance']);

For 422s, assert errors is nested in the problem object and that status is an integer (RFC says integer, not string — a common bug in hand-rolled implementations).

OpenAPI / client contract. Treat Problem as a schema component in your OpenAPI spec and reference it in every 4xx/5xx response. Generated clients can then type the error body correctly.

Gotchas and Tips

  • Media type, not just JSON. The whole point of RFC 9457 is Content-Type: application/problem+json. If your package or your own wrapper returns application/json, strict clients and some API gateways won't recognise it. Verify with assertHeader.
  • Exception handler ordering. Laravel's default handler renders ValidationException and HttpException before your render callback in some Laravel versions. Use the package's rendering hook if it provides one (check the provider), or register your callback early and return null to fall through.
  • abort(404) and HttpException. abort() throws HttpException. Unless the package maps HttpException to a Problem, those responses will keep Laravel's default shape. Explicitly handle HttpException in the renderer and translate status → default title (NotFound → "Not Found", etc.).
  • type semantics. RFC 9457 defaults type to about:blank when absent, and says title should be stable for that type. Don't put variable data (IDs, messages) in title; put it in detail. If you don't have a public docs URI for problem types, about:blank is acceptable — but pick one convention and stay consistent.
  • instance should be request-specific (e.g. request()->path()), not the problem type URI. Confusing these is the most common mistake in hand-rolled implementations.
  • Don't leak internals in production. Check whether the package or your renderer surfaces getMessage(), stack traces, or SQL in detail when APP_DEBUG=true. Force a generic detail in production, and log the real exception. The status and title are safe to expose; detail and extensions are not, by default.
  • Small-package risk is real. 11 stars, one maintainer, MIT. Pin the version in composer.json, read the source before relying on it in a public API, and consider wrapping it behind your own ProblemFactory / ApiExceptionRenderer so swapping it later is a one-file change. If the package's API surface is thin, you may end up re-implementing parts of it — that's fine, but do it behind your own interface.
  • Extensibility points to look for in the provider: response macros (Response::macro('problem', ...)), a container binding for the Problem class you can override, and configuration for base type URIs. Override via config or container rather than forking.
  • CORS and Content-Type. Browsers only expose Content-Type to JS if it's in Access-Control-Expose-Headers — usually not needed since Content-Type is a safelisted response header, but if clients read custom extension headers, add them there.
  • Version pin + upgrade check. Treat the package like any small dependency: run composer outdated in CI, and add one integration test per exception type you map. If the package releases a breaking change, that test suite tells you immediately whether your /api/* contract moved.
  • Laravel 10 vs 11 divergence. Handler registration moved from app/Exceptions/Handler.php to bootstrap/app.php. Confirm the package supports your version before adopting it on a mixed-version codebase.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource