alexxvl / problem-details
RFC 9457 Problem Details for Laravel APIs
Requires
- php: ^8.2
- illuminate/auth: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- illuminate/validation: ^11.0|^12.0|^13.0
- symfony/http-kernel: ^7.0|^8.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-12 21:12:28 UTC
README
RFC 9457 Problem Details responses for Laravel APIs.
The package is hosted at github.com/AlexXVL/problem-details.
Requirements
- PHP 8.2+
- Laravel 11, 12 or 13
Installation
composer require alexxvl/problem-details
The service provider is registered automatically through Laravel package discovery. If package discovery is disabled, register it manually:
AlexXVL\ProblemDetails\ProblemDetailsServiceProvider::class
Development from source
To install from the repository instead of Packagist, add a VCS repository to
your application's composer.json:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/AlexXVL/problem-details"
}
]
}
Then require a tagged release:
composer require alexxvl/problem-details:^0.1
The constraint resolves against a tag (for example, v0.1.0).
For local development while the package lives next to the application, use a path repository instead:
{
"repositories": [
{
"type": "path",
"url": "../packages/problem-details",
"options": {
"symlink": true
}
}
]
}
Then require the development branch:
composer require alexxvl/problem-details:@dev
Configuration
Publish the package configuration:
php artisan vendor:publish --tag=problem-details-config
The api_prefix option in config/problem-details.php controls which requests
are converted by the framework exception renderer. The default is api, which
matches both /api and /api/v1/.... Set it to an empty string to convert
framework exceptions for every request. Exceptions extending BaseException
are rendered on every request regardless of this setting.
Usage
Extend the base exception in your application module:
namespace App\Modules\User\Exceptions; use AlexXVL\ProblemDetails\Exceptions\BaseException; abstract class UserException extends BaseException { }
Define a concrete error:
namespace App\Modules\User\Exceptions; use Symfony\Component\HttpFoundation\Response; final class UserNotFoundException extends UserException { protected int $statusCode = Response::HTTP_NOT_FOUND; protected ?string $errorCode = 'USER_NOT_FOUND'; public function getType(): string { return url('/problems/user-not-found'); } public function getTitle(): string { return 'User Not Found'; } }
Throw it from a service or repository:
throw new UserNotFoundException("User {$uuid} not found");
title describes the problem type and should stay stable. Exception::$message
is exposed as the RFC detail member and should describe the concrete
occurrence. If no message is provided, detail is omitted.
Validation errors
AlexXVL\ProblemDetails\Exceptions\ValidationException is available for
application-level validation errors:
throw new ValidationException($validator->errors()->toArray());
It renders a 422 response with the errors extension member:
{
"type": "/problems/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "Validation failed",
"instance": "/api/v1/users",
"code": "VALIDATION_ERROR",
"errors": {
"email": ["The email field is required."]
}
}
The example above shows the default relative type used by an
application-thrown exception. When Laravel's own ValidationException is
converted by the renderer, the type is resolved through the application URL
generator. You can also pass a custom type explicitly:
throw new ValidationException( $validator->errors()->toArray(), type: 'https://example.com/problems/validation-error', );
Laravel's own ValidationException, AuthenticationException and
HttpExceptionInterface exceptions are converted automatically.
Response format
{
"type": "http://localhost/problems/user-not-found",
"title": "User Not Found",
"status": 404,
"detail": "User 123 not found",
"instance": "/api/v1/users/123",
"code": "USER_NOT_FOUND"
}
The response uses Content-Type: application/problem+json.
Extension members:
code— stable machine-readable error code;errors— field validation errors.
Notes
- Framework exceptions (validation, authentication and HTTP errors) are
converted only for requests matching the configured
api_prefix(defaultapi), either exactly or with subpaths. Other requests are handled by Laravel as usual. - Exceptions extending
BaseExceptionrender as Problem Details on every request because they implement their ownrender()method. - 4xx business exceptions are not reported to the logger.
- 404 responses keep the original
detailwhenapp.debugis enabled. In production the detail is replaced withNot Foundso framework and model internals are not exposed. Exceptions extendingBaseExceptionkeep their owndetailon every request. - 5xx responses hide exception details when
app.debugis disabled.
Development
composer install
composer test