skip to content

In Django, how do you set and later remove a cookie from a view with HttpResponse.set_cookie() and delete_cookie()?

level: juniorimportance: should knowfreq 42%

answer

  1. cookies live on the response
  2. request.COOKIES on the next request
  3. max_age versus expires
  4. delete must match path and domain

basics

~10 s

Call response.set_cookie(key, value, max_age=...) on the response you return, then read it from request.COOKIES on later requests. Remove it with response.delete_cookie(key), using the same path and domain it was set with.

solid answer

~40 s

Cookies are written on the **response**, not the request: build the `HttpResponse`, call `response.set_cookie("warehouse", "north", max_age=3600)`, and return it. The browser sends it back and you read it from `request.COOKIES` on the **next** request, not the current one. `max_age` takes seconds or a `timedelta`; `expires` takes a date string or a `datetime`; with neither, the cookie lasts only for the browser session. Passing both `max_age` and a `datetime` for `expires` raises `ValueError`. `path` defaults to `"/"` and `domain` to the current host. `delete_cookie(key, path=..., domain=...)` sends the same name with `max_age=0` and a 1970 expiry, so its `path` and `domain` must match the originals or the browser keeps the old cookie.

code

python · 16 lines
python
from datetime import timedelta

from django.shortcuts import render


def stock_overview(request, code):
    last = request.COOKIES.get("last_warehouse")  # value from a previous response
    response = render(request, "stock/overview.html", {"code": code, "last": last})
    response.set_cookie("last_warehouse", code, max_age=timedelta(days=30))
    return response


def forget_warehouse(request):
    response = render(request, "stock/forgotten.html")
    response.delete_cookie("last_warehouse")  # same default path "/" as when set
    return response

go deeper

for a junior

Recall that set_cookie and delete_cookie are response methods, and that request.COOKIES only shows cookies the browser sent back.

for a middle

Explain max_age versus expires, the session-cookie default, and why delete_cookie must repeat the original path and domain.

for a senior

Spot cookie bugs from path or domain mismatches and keep tamper-sensitive data out of plain cookies in favour of signed values or sessions.

for a principal

Decide what state a product may keep client-side at all, weighing size limits, privacy review and the cost of moving data server-side.

## Cookies belong to the response An HTTP cookie is set by a `Set-Cookie` header on a response and echoed back by the browser in the `Cookie` header of later requests. Django mirrors that split: - **writing** happens on any `HttpResponse` (or subclass) through `response.set_cookie()`; - **reading** happens on `HttpRequest` through the dict-like `request.COOKIES`. A frequent junior mistake is to call `set_cookie()` and then look for the value in `request.COOKIES` inside the same view. It is not there: the browser has not received it yet. The value appears on the next request. Because the method lives on the response, you need a response object in hand. With `render()` or `redirect()` you assign the result to a variable, call `set_cookie()` on it, and return it. ## The set_cookie signature The full signature in Django 6.1 is `set_cookie(key, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite=None)`. | Argument | Meaning | Default | |---|---|---| | `max_age` | lifetime in seconds, or a `timedelta` | `None` (session cookie) | | `expires` | `"Wdy, DD-Mon-YY HH:MM:SS GMT"` string or a `datetime` | `None` | | `path` | URL prefix the browser sends the cookie for | `"/"` | | `domain` | domain the cookie is shared across | `None` (current host only) | | `secure`, `httponly`, `samesite` | transport and script-access flags | off / `None` | The lifetime rules are worth stating precisely: 1. If `max_age` is given, Django writes `Max-Age` and also computes a matching `expires` date, because some older clients only understand `expires`. 2. If `expires` is a `datetime` (a naive one is treated as UTC), Django converts it to `max_age` itself; passing a `max_age` at the same time raises `ValueError` ("'expires' and 'max_age' can't be used together"). 3. If neither is given, the cookie is a **session cookie**: the browser drops it when the browsing session ends. `samesite` accepts only `"Lax"`, `"Strict"` or `"None"` (case-insensitive); anything else raises `ValueError`. What those flags protect against and which values a project should standardise on is a security topic of its own. The value is stored as a string. Browsers commonly cap a cookie at about 4096 bytes and Django does not raise when you exceed that, so a large value may silently fail to be stored. ## Deleting a cookie There is no "delete" instruction in HTTP. `delete_cookie(key, path="/", domain=None, samesite=None)` calls `set_cookie()` for the same name with an empty value, `max_age=0` and `expires="Thu, 01 Jan 1970 00:00:00 GMT"`, which tells the browser to discard it. The browser identifies a cookie by name **plus** domain **plus** path. So: - a cookie set with `path="/reports/"` must be deleted with `path="/reports/"`; - a cookie set with `domain="example.com"` must be deleted with the same `domain`; - a mismatch leaves the original cookie in place and adds nothing useful. `delete_cookie()` also turns on the `Secure` attribute automatically when the name starts with `__Secure-` or `__Host-`, or when `samesite="None"`, because browsers ignore an insecure `Set-Cookie` for those cases. ## Edge cases worth knowing - **Redirects carry cookies too.** `set_cookie()` works on an `HttpResponseRedirect`, so a view can set a preference and redirect in one response. - **Setting the same key again replaces its value** within one response, because each response keeps one entry per cookie name; you cannot send two cookies of the same name for different paths in one response. - **A `datetime` in the past** produces `max_age=0` (Django clamps the computed value at zero), which makes the browser drop the cookie, the same effect as `delete_cookie()`. - **`max_age` as a `timedelta`** is converted to whole seconds, so `timedelta(days=30)` and `2592000` are equivalent. - **The value is not protected.** The user can read and edit it in the browser, so treat anything read from `request.COOKIES` as untrusted input and validate it like a query parameter. - **Reading in tests.** The test client keeps cookies between requests, and a response's `cookies` attribute exposes what was set, so `response.cookies["last_warehouse"].value` is easy to assert. ## Putting it together A typical flow for remembering which warehouse a stock clerk last viewed: 1. the view renders the page into `response`; 2. it calls `response.set_cookie("last_warehouse", code, max_age=timedelta(days=30))`; 3. a later view reads `request.COOKIES.get("last_warehouse")` and falls back to a default; 4. a "forget" view calls `response.delete_cookie("last_warehouse")`. For anything that must not be tampered with, a plain cookie is the wrong tool: its value is readable and editable by the user. Django offers signed cookies and server-side sessions for that; they are separate mechanisms built on top of this one.

  • A cookie set with path="/reports/" survives a call to delete_cookie("filter"). Why?
    `delete_cookie()` defaults to `path="/"`, so it expires a different cookie: browsers key cookies by name, domain and path together. Call `delete_cookie("filter", path="/reports/")` (and the same `domain`, if one was used) so the expiring `Set-Cookie` targets the original.
  • What happens if you pass both max_age=3600 and expires=a datetime to set_cookie()?
    Django raises `ValueError("'expires' and 'max_age' can't be used together.")`. When `expires` is a `datetime`, Django derives `max_age` from it, so a second lifetime would be ambiguous. Pass one or the other; a string `expires` is written as-is instead.

saying these in an interview costs you the question

  • A cookie set on the response is readable in request.COOKIES in the same view
  • set_cookie is a method on the request object
  • delete_cookie removes the cookie regardless of the path it was set with
  • Leaving out max_age and expires makes the cookie permanent
  • Django raises an error if a cookie value exceeds 4096 bytes