Response headers and cookies
Intermediate5 min read·lv-10-003
Concept
Response headers communicate metadata about the response to the client — caching instructions, content type, security policies, pagination links, and custom application data.
Setting headers in Laravel:
response()->header('X-Custom', 'value'): Set a single header.response()->withHeaders(['X-One' => 'a', 'X-Two' => 'b']): Set multiple headers.- In middleware:
$response->headers->set('X-Header', 'value'). - Via
Response::macro(): Define a custom response method.
Important response headers:
Content-Type: Charset matters:text/html; charset=UTF-8. Laravel sets this automatically.Cache-Control: Browser caching.no-storeprevents any caching.public, max-age=3600caches for 1 hour.private, no-cacherequires revalidation.ETag: Version identifier for conditional requests. Client sendsIf-None-Matchheader; if ETag matches, return304 Not Modified.Last-Modified: Date of last modification. Client sendsIf-Modified-Since.X-RateLimit-Limit/X-RateLimit-Remaining/Retry-After: Rate limiting signals.Content-Disposition: attachment; filename="file.pdf": Forces download.
Response cookies: response()->cookie($name, $value, $minutes, $path, $domain, $secure, $httpOnly). Or use the Cookie facade to queue cookies.
Signed response macros: Define reusable response patterns: Response::macro('apiResponse', fn($data) => response()->json([...])).
Code Example
php
<?php
// Setting response headers
return response()->json($data)
->header('X-Custom-Header', 'value')
->header('Cache-Control', 'public, max-age=3600')
->withHeaders([
'X-Frame-Options' => 'DENY',
'X-Request-Id' => $requestId,
]);
// Middleware — add headers to ALL responses
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-App-Version', config('app.version'));
$response->headers->remove('X-Powered-By');
return $response;
}
// Cache-Control headers for API responses
// No caching (default for authenticated API responses)
return response()->json($data)->header('Cache-Control', 'no-store, private');
// Public cache with ETag
$etag = md5($user->updated_at . $user->id);
if ($request->header('If-None-Match') === $etag) {
return response()->noContent()->setStatusCode(304);
}
return response()->json($user)
->header('ETag', $etag)
->header('Cache-Control', 'public, max-age=300');
// Pagination link headers (REST standard)
$paginator = Order::paginate(15);
return response()->json($paginator->items())
->header('X-Total-Count', $paginator->total())
->header('X-Per-Page', $paginator->perPage())
->header('X-Current-Page', $paginator->currentPage())
->header('Link', implode(', ', array_filter([
$paginator->previousPageUrl() ? "<{$paginator->previousPageUrl()}>; rel=\"prev\"" : null,
$paginator->nextPageUrl() ? "<{$paginator->nextPageUrl()}>; rel=\"next\"" : null,
])));
// Response macro — custom response format
\Illuminate\Support\Facades\Response::macro('api', function(mixed $data, int $status = 200) {
return response()->json([
'success' => $status < 400,
'data' => $data,
'meta' => ['request_id' => request()->id ?? null],
], $status);
});
// Usage: return response()->api($user); or response()->api($errors, 422);