Response macros for consistent, standardized API responses in Laravel
composer require philiprehberger/laravel-response-macrosResponse macros for consistent, standardized API responses in Laravel.
composer require philiprehberger/laravel-response-macros
The service provider is auto-discovered by Laravel. No manual registration is needed.
php artisan vendor:publish --tag=response-macros-config
This copies config/response-macros.php to your application's config directory.
// 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
}
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');
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
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
}
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.
| 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 |
composer install
vendor/bin/phpunit
vendor/bin/pint --test
vendor/bin/phpstan analyse
If you find this project useful: