In Laravel, how do you set a cookie with response()->cookie() or withCookie() versus Cookie::queue(), and when do you need each?
answer
- have the response yet, or not
- cookie($name, $value, $minutes)
- 0 minutes means a browser-session cookie
- queued cookies added by AddQueuedCookiesToResponse
- withoutCookie() and Cookie::expire() delete
basics
~20 sChain ->cookie() or ->withCookie() when you hold the response object. Use Cookie::queue() when the code setting the cookie runs before the response exists; the web group's AddQueuedCookiesToResponse middleware adds queued cookies to whatever response comes back.
solid answer
~40 s`$response->cookie('seat_hold', $id, 10)` is an alias of `withCookie()`: given a name it calls the `cookie()` helper to build a Symfony `Cookie` (value, lifetime in **minutes**, then path, domain, secure, httpOnly, raw, sameSite) and sets it on the response headers. That needs the response in hand. When a service or listener decides on a cookie before the controller has built anything, `Cookie::queue('seat_hold', $id, 10)` stores it in the cookie jar, and the `AddQueuedCookiesToResponse` middleware in the `web` group copies every queued cookie onto the outgoing response. A lifetime of `0` means a session cookie that dies with the browser; `Cookie::forever()` means 400 days. Path, domain, secure and same-site default to the values in `config/session.php`. To delete, call `withoutCookie('seat_hold')` or `Cookie::expire('seat_hold')`.
code
php · 12 lines<?php
use Illuminate\Support\Facades\Cookie;
// Deep in a service: no response exists yet.
Cookie::queue('seat_hold', $hold->id, 10); // 10 minutes
// In a controller: chain onto the response.
return response()
->view('venues.chosen', ['venue' => $venue])
->cookie('venue_pref', $venue->slug, 60 * 24 * 30)
->withoutCookie('seat_hold');go deeper
Know the two styles: chain cookie() or withCookie() on a response, or call Cookie::queue() when there is no response yet. The lifetime is in minutes.
Explain that queued cookies are copied onto the response by AddQueuedCookiesToResponse in the web group, that 0 minutes means a session cookie, and where default attributes come from.
Keep one mechanism per cookie, delete with withoutCookie() or Cookie::expire(), and check which middleware group a route runs in before relying on the queue.
Set team conventions for which state lives in cookies versus the session, and keep cookie defaults centralized in session config rather than repeated per call.
## Two ways to get a cookie onto a response A cookie reaches the browser as a `Set-Cookie` header on a response. Laravel lets you put it there in two ways, and the difference is simply **whether you have the response object yet**. In a concert-venue app, a buyer who picks a seat gets a short-lived `seat_hold` cookie, and the app remembers the preferred venue in a long-lived `venue_pref` cookie. ## Attaching directly to a response Every `Illuminate\Http\Response`, `JsonResponse` and `RedirectResponse` has these methods from `ResponseTrait`: - `cookie($cookie, ...)` is an alias that forwards all its arguments to `withCookie()`. - `withCookie($cookie, ...)` accepts a ready `Symfony\Component\HttpFoundation\Cookie`, or a name followed by the `cookie()` helper's arguments, and calls `$this->headers->setCookie()`. - `withCookies([...])` sets several `Cookie` instances at once. - `withoutCookie($name, $path, $domain)` sends the cookie again with an expiry far in the past, which deletes it in the browser. The `cookie()` helper, and `Cookie::make()`, take `($name, $value, $minutes = 0, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null)`. Two defaults matter: 1. **Minutes, not seconds.** `cookie('seat_hold', $id, 10)` lasts ten minutes. A lifetime of `0` produces a session cookie with no expiry date, which the browser drops when it closes. `Cookie::forever()` uses 576000 minutes, which is 400 days. 2. **Unset attributes come from session config.** The cookie jar is created with the `path`, `domain`, `secure` and `same_site` values from `config/session.php`, so a bare `cookie('venue_pref', 'north-hall', 60)` inherits them. `httpOnly` defaults to `true`, so JavaScript cannot read it. ## Queueing when no response exists yet Often the code that decides on a cookie is not the code that builds the response: a seat-hold service called from a controller, an event listener, or a helper deep in the stack. For those, the `Cookie` facade offers a queue: - `Cookie::queue('seat_hold', $id, 10)` builds the cookie with the same arguments as `make()` and stores it, keyed by name and path, in the singleton `CookieJar`. - `Cookie::queue($cookieInstance)` queues a cookie you already built. - `Cookie::expire('seat_hold')` queues a deletion. - `Cookie::unqueue('seat_hold')` cancels a queued cookie. Nothing is sent at that moment. The `Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse` middleware runs `$next($request)`, takes the finished response and calls `$response->headers->setCookie()` for every queued cookie. That middleware sits in the default `web` group, just after `EncryptCookies`. ## Choosing between them | Situation | Use | |---|---| | The controller returns a response and knows the cookie | `->cookie()` or `->withCookie()` | | A service or listener sets it before any response exists | `Cookie::queue()` | | Delete while building the response | `->withoutCookie('name')` | | Delete from deep inside the request | `Cookie::expire('name')` | | Long-lived preference | `Cookie::forever()` or a large minutes value | ## Reading them back On the next request, `$request->cookie('venue_pref')` returns the value. In the `web` group the `EncryptCookies` middleware encrypts outgoing cookie values and decrypts incoming ones, so the browser only ever sees ciphertext; that middleware and its exceptions are part of the default stack, and the meaning of `Secure`, `HttpOnly` and `SameSite` belongs to HTTP cookies in general. ## Common mistakes - **Seconds instead of minutes.** `cookie('seat_hold', $id, 600)` keeps a hold for ten hours, not ten minutes. - **Chaining onto a throwaway response.** Calling `response()->cookie(...)` in a service creates a new response that nobody returns; queue the cookie instead. - **Deleting with an empty value.** Setting the value to `''` still leaves a cookie behind; `withoutCookie()` or `Cookie::expire()` sends an expiry in the past so the browser removes it. - **Deleting with the wrong path or domain.** A cookie set with a custom path must be expired with the same path, or the browser keeps the original. - **Assuming the queue works everywhere.** It depends on the route running the `web` middleware group. ## What interviewers probe They want the "do you have the response yet" distinction, the minutes unit and zero meaning session cookie, and an awareness that queued cookies depend on a middleware in the `web` group. The best answers also know how to delete a cookie in both styles.
- Why does calling `Cookie::queue('seat_hold', ...)` twice in one request send only one seat_hold cookie?`CookieJar::queue()` stores cookies in an array keyed by name and then by path, so a second call with the same name and path overwrites the first. Only the last value reaches `AddQueuedCookiesToResponse`. Use `Cookie::unqueue()` to cancel a queued cookie before queueing a replacement if the intent should be explicit.
- Why does `cookie('venue_pref', 'north-hall')` disappear when the user restarts the browser?The third argument, minutes, defaults to 0, and the cookie jar turns 0 into an expiry of 0: a session cookie with no Expires attribute. Browsers discard those when the session ends. Pass a lifetime in minutes, or use `Cookie::forever()`, for a persistent preference.
saying these in an interview costs you the question
- The cookie lifetime argument is in seconds.
- A lifetime of 0 makes the cookie permanent.
- Cookie::queue() sends the Set-Cookie header immediately.
- You must pass path, domain and secure every time you create a cookie.
- A cookie can only be deleted by setting its value to an empty string.