skip to content

In a browser, what exactly does reading `document.cookie` give you, and how would you extract the value of a single cookie named `theme` from it?

level: juniorimportance: must knowfreq 74%

answer

  1. one flat string, not an object
  2. pairs joined by semicolon-space
  3. attributes never come back
  4. HttpOnly ones are simply absent
  5. split, match whole name, then decode

basics

~20 s

Reading document.cookie returns one flat string of every cookie the current page may read, as name=value pairs joined by "; ". Attributes and HttpOnly cookies are absent, so extracting one value means splitting and decoding the string yourself.

solid answer

~50 s

`document.cookie` is an accessor property that returns a single string, not an object or a list. It contains only the `name=value` pairs of cookies that match the current document's URL, joined by `"; "` — for example `theme=dark; lang=en`. Three things are missing and this is the part people forget: attributes such as `Path`, `Domain`, `Expires`/`Max-Age`, `Secure` and `SameSite` are never returned, so you cannot read back when a cookie expires; cookies marked `HttpOnly` by the server are invisible to script entirely; and `Secure` cookies do not appear on a non-secure page. To read one cookie I split on `"; "`, find the entry whose name matches exactly, take everything after the first `=`, and run `decodeURIComponent` on it. Matching with a substring search is the classic bug — searching for `id=` also hits `uid=`.

code

javascript · 12 lines
javascript
document.cookie = 'theme=dark; path=/; max-age=31536000';
document.cookie = 'lang=' + encodeURIComponent('en-GB') + '; path=/';

function readCookie(name) {
  const prefix = encodeURIComponent(name) + '=';
  const hit = document.cookie.split('; ').find((p) => p.startsWith(prefix));
  return hit ? decodeURIComponent(hit.slice(prefix.length)) : undefined;
}

console.log(document.cookie);   // "theme=dark; lang=en-GB"
console.log(readCookie('theme')); // "dark"
console.log(readCookie('them'));  // undefined, not a partial match

go deeper

for a junior

Know that it is one string of name=value pairs joined by "; ", that you must split and decode it yourself, and that matching a name with a substring search is a bug.

for a middle

Be ready to explain the filters the getter applies — URL scope, HttpOnly, Secure on insecure pages — and why attributes are write-only, so expiry can never be read back.

for a senior

Show that you would wrap parsing in one tested helper rather than scattering split calls, and that you weigh the per-request byte cost of every cookie you add against putting the value in another store.

for a principal

Own the policy question: which values belong in cookies at all, given that each one is transmitted on every matching request and only the server can hold the HttpOnly ones your scripts must never touch.

## What the property actually is `document.cookie` is an accessor property on `Document` with both a getter and a setter, and the two do completely different jobs. The getter serialises the browser's cookie store for the current document into **one string**. The setter parses **one cookie** out of a string you assign. Nothing about the API is object-shaped: there is no `document.cookies` collection, no `.get(name)`, no length. The returned string is a list of `name=value` pairs separated by a semicolon and a space, with no trailing separator: ```js document.cookie; // "theme=dark; lang=en; cart=a%2Cb" ``` If no cookie is readable, you get the empty string `""` — not `null`, not `undefined`. ## What the browser filters out before you see it The getter is a filtered view, and four filters apply: - **Scope matching.** Only cookies whose domain and path match the current document's URL are included. A cookie stored for `Path=/admin` simply is not there while you are on `/home`. - **`HttpOnly`.** A cookie the server marked `HttpOnly` never appears in `document.cookie`, no matter what. This is usually the session cookie — the one you most want to read is the one you cannot. - **`Secure` on an insecure page.** A `Secure` cookie is withheld from a page served over plain `http:`. On `https:` it reads normally; `Secure` restricts transport, not scripting. - **All attributes.** The getter emits names and values only. `Expires`, `Max-Age`, `Path`, `Domain`, `Secure`, `SameSite` are write-only from JavaScript's point of view: you can set them, and you can never read them back. There is no way to ask `document.cookie` "when does this expire?" or "which path is this scoped to?". A consequence of the last two points: if two cookies share a name but differ in path (`/` and `/app`), both appear in the string as identical-looking `name=value` entries and you cannot tell which is which. ## Parsing it correctly ```js function readCookie(name) { const prefix = encodeURIComponent(name) + '='; const hit = document.cookie .split('; ') .find((pair) => pair.startsWith(prefix)); return hit ? decodeURIComponent(hit.slice(prefix.length)) : undefined; } ``` Three details matter here. **Match the whole name**, anchored with `startsWith` after splitting — a bare `document.cookie.includes('id=')` also matches `uid=` and `session_id=`. **Split the value at the first `=` only**, because a value may legitimately contain `=` (base64 padding is the usual culprit). And **decode**: cookie values cannot carry `;`, `,`, whitespace or control characters literally, so the near-universal convention is to `encodeURIComponent` on write and `decodeURIComponent` on read. The platform does not do this for you — `document.cookie` hands back exactly the bytes stored. ## Why the string can be surprisingly large Every cookie visible here is also attached by the browser to matching requests, so the cookie jar is request overhead on every navigation and fetch, not just client state. Browsers cap roughly 4 KB per cookie and a few dozen per domain; exceeding the cap silently drops the write rather than throwing. That is the main argument for not using cookies as a general client-side store when the server does not need the value. ## Reading is synchronous and re-parses each time The getter builds the string on every access, so a loop that reads `document.cookie` once per iteration is doing repeated work; read it once into a local variable and parse that. It is also fully synchronous on the main thread and unavailable in workers, which is precisely the gap the newer promise-based cookie API was designed to close. ## The practical summary Treat `document.cookie` as a serialised, filtered, attribute-free snapshot of the subset of cookies this document is allowed to see. Anything you need to know beyond a name and a value — expiry, scope, whether it exists at all in the HttpOnly case — has to come from somewhere other than this property.

  • Why can a substring search like `document.cookie.includes('id=')` return a false positive, and what does that break in practice?
    Because the string is one flat concatenation, `id=` also matches inside `uid=7` or `session_id=abc`. Code that then slices from that index reads a neighbouring cookie's value, so a feature flag or user id silently comes back wrong for some users and right for others depending on which cookies happen to exist. Split on `"; "` first and compare the full name.
  • You need to know whether a cookie you wrote a moment ago expires today. Can you get that from `document.cookie`?
    No. The getter returns names and values only; `Expires` and `Max-Age` are write-only through this API and are never echoed back. If your code needs the expiry it has to store it separately — as part of the value, or alongside it in another store — or use the promise-based cookie API, whose returned objects do carry the expiry.
  • Two cookies both named `token` exist, one scoped to `/` and one to `/app`. What does reading `document.cookie` on `/app` show?
    Both, as two indistinguishable `token=...` entries, because path scoping is a match filter and not something the getter exposes. Order is not something you should rely on. This ambiguity is exactly why writing and deleting cookies requires you to track the path yourself — the read side gives you no way to recover it.

It is a single sticky note listing labels and values, not a filing cabinet you can query: the folders' tabs — where each item is filed and when it gets shredded — are not written on the note.

saying these in an interview costs you the question

  • Thinks document.cookie returns an object or an array of cookies
  • Expects Expires, Path or Secure to be readable from JavaScript
  • Assumes every cookie the server set shows up in the string
  • Uses indexOf or includes, matching a cookie whose name is a suffix
  • Forgets decodeURIComponent, so encoded values leak %2C into the UI

context