philiprehberger / laravel-response-macros
Response macros for consistent, standardized API responses in Laravel
Package info
github.com/philiprehberger/laravel-response-macros
pkg:composer/philiprehberger/laravel-response-macros
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^11.0
README
Response macros for consistent, standardized API responses in Laravel.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require philiprehberger/laravel-response-macros
The service provider is auto-discovered by Laravel. No manual registration is needed.
Publish the config (optional)
php artisan vendor:publish --tag=response-macros-config
This copies config/response-macros.php to your application's config directory.
Usage
Configuration
// config/response-macros.php return [ // Key used to wrap data in the envelope() macro 'envelope_key' => 'data', // Key used to nest metadata in the envelope() macro 'meta_key' => 'meta', // When true, each response body includes a "status" key mirroring the HTTP status code 'include_status_code' => true, ];
response()->success()
Returns a 200 OK response (or any 2xx) indicating a successful operation.
Signature
response()->success(mixed $data = null, string $message = 'Success', int $status = 200): JsonResponse
Note: The
$statusparameter must be a 2xx status code (200–299). Passing a non-2xx status throwsInvalidArgumentException.
Example
return response()->success($user, 'User retrieved successfully');
Response
{
"success": true,
"message": "User retrieved successfully",
"data": { "id": 1, "name": "Jane Doe" },
"status": 200
}
response()->error()
Returns a 400 Bad Request response (or any 4xx/5xx) indicating a failed operation.
Signature
response()->error(string $message = 'Error', int $status = 400, mixed $errors = null): JsonResponse
Note: The
$statusparameter must be a 4xx or 5xx status code (400–599). Passing a non-error status throwsInvalidArgumentException.
Example
return response()->error('Resource not found', 404);
Response
{
"success": false,
"message": "Resource not found",
"errors": null,
"status": 404
}
With additional error detail:
return response()->error('Payment failed', 402, ['code' => 'card_declined']);
{
"success": false,
"message": "Payment failed",
"errors": { "code": "card_declined" },
"status": 402
}
response()->paginated()
Wraps a LengthAwarePaginator with standardized pagination metadata.
Signature
response()->paginated(LengthAwarePaginator $paginator, string $message = 'Success'): JsonResponse
Example
$users = User::paginate(15); return response()->paginated($users, 'Users retrieved');
Response
{
"success": true,
"message": "Users retrieved",
"data": [ ... ],
"meta": {
"current_page": 1,
"last_page": 4,
"per_page": 15,
"total": 60
},
"status": 200
}
response()->validationError()
Returns a 422 Unprocessable Entity response from a Validator instance or a MessageBag.
Signature
response()->validationError(Validator|MessageBag $validator, string $message = 'The given data was invalid.'): JsonResponse
Example with a Validator
$validator = Validator::make($request->all(), [ 'email' => 'required|email', 'name' => 'required|string|max:255', ]); if ($validator->fails()) { return response()->validationError($validator); }
Example with a MessageBag
$messages = new \Illuminate\Support\MessageBag([ 'email' => ['This email address is already taken.'], ]); return response()->validationError($messages);
Response
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."],
"name": ["The name field is required."]
},
"status": 422
}
You can customize the error message:
return response()->validationError($validator, 'Please fix the highlighted fields.');
response()->noContent()
Removed in v1.1.0. The
noContent()macro was dead code — Laravel'sResponseFactorydefinesnoContent()natively, and native methods take precedence over macros. Use Laravel's built-inresponse()->noContent()instead, which returns an HTTP204with an empty body.
response()->accepted()
Returns a 202 Accepted response indicating the request has been queued or is being processed asynchronously.
Signature
response()->accepted(mixed $data = null, string $message = 'Accepted'): JsonResponse
Example
ProcessReportJob::dispatch($report); return response()->accepted(['job_id' => $job->id], 'Report generation queued');
Response
{
"success": true,
"message": "Report generation queued",
"data": { "job_id": "abc-123" },
"status": 202
}
Cursor Pagination
Wraps a CursorPaginator with cursor-based pagination metadata. Ideal for infinite-scroll UIs and large datasets where offset pagination is impractical.
Signature
response()->cursorPaginated(CursorPaginator $paginator, string $wrap = 'data', int $status = 200): JsonResponse
Example
$users = User::cursorPaginate(15); return response()->cursorPaginated($users);
Response
{
"data": [ ... ],
"meta": {
"next_cursor": "eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
"prev_cursor": null,
"has_more": true,
"per_page": 15
}
}
You can customize the wrap key:
return response()->cursorPaginated($users, 'results');
Rate Limit Headers
Adds standard rate-limiting headers to a response. Chain it after building a JSON response or call it directly from the response factory.
Signature
response()->withRateLimit(int $limit, int $remaining, ?int $retryAfter = null): JsonResponse
Example
return response()->withRateLimit(100, 97);
Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
When the client should back off:
return response()->withRateLimit(100, 0, 60);
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 60
X-RateLimit-Reset: 1711929600
Cached Responses
Returns a JSON response with Cache-Control headers and optional ETag support including automatic 304 Not Modified handling.
Signature
response()->cached(mixed $data, int $ttl = 3600, ?string $etag = null): JsonResponse
Example without ETag
return response()->cached($config, 1800);
Headers
Cache-Control: public, max-age=1800
Example with ETag
$etag = md5(json_encode($data)); return response()->cached($data, 3600, $etag);
When the client sends If-None-Match matching the ETag, a 304 Not Modified response is returned automatically with no body.
response()->envelope()
Wraps arbitrary data under a configurable key with optional metadata. Useful when you need full control over the response shape without the opinionated success/message fields.
Signature
response()->envelope(mixed $data, array $meta = []): JsonResponse
Example without metadata
return response()->envelope($product);
Response
{
"data": { "id": 42, "name": "Widget Pro" },
"status": 200
}
Example with metadata
return response()->envelope($results, [ 'version' => '2.1', 'locale' => 'en-US', 'cached' => true, ]);
Response
{
"data": [ ... ],
"meta": {
"version": "2.1",
"locale": "en-US",
"cached": true
},
"status": 200
}
Omitting the Status Code from the Body
Set include_status_code to false in config/response-macros.php to remove the "status" key from all response bodies:
'include_status_code' => false,
Before:
{ "success": true, "message": "OK", "data": null, "status": 200 }
After:
{ "success": true, "message": "OK", "data": null }
The HTTP status code on the response itself is never affected by this option.
API
| Macro | Signature | Description |
|---|---|---|
response()->success() |
success(mixed $data, string $message, int $status): JsonResponse |
2xx success response |
response()->error() |
error(string $message, int $status, mixed $errors): JsonResponse |
4xx/5xx error response |
response()->paginated() |
paginated(LengthAwarePaginator $paginator, string $message): JsonResponse |
Paginated response with metadata |
response()->validationError() |
validationError(Validator|MessageBag $validator, string $message): JsonResponse |
422 validation error |
response()->accepted() |
accepted(mixed $data, string $message): JsonResponse |
202 async accepted response |
response()->envelope() |
envelope(mixed $data, array $meta): JsonResponse |
Data under configurable key |
response()->cursorPaginated() |
cursorPaginated(CursorPaginator $paginator, string $wrap, int $status): JsonResponse |
Cursor-paginated response with metadata |
response()->withRateLimit() |
withRateLimit(int $limit, int $remaining, ?int $retryAfter): JsonResponse |
Adds X-RateLimit-* headers |
response()->cached() |
cached(mixed $data, int $ttl, ?string $etag): JsonResponse |
Cached response with ETag and 304 support |
Development
composer install vendor/bin/phpunit vendor/bin/pint --test vendor/bin/phpstan analyse
Support
If you find this project useful: