0

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-store prevents any caching. public, max-age=3600 caches for 1 hour. private, no-cache requires revalidation.
  • ETag: Version identifier for conditional requests. Client sends If-None-Match header; if ETag matches, return 304 Not Modified.
  • Last-Modified: Date of last modification. Client sends If-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);