skip to content

The `cookieStore` API is offered as a modern replacement for `document.cookie`. What does it change about reading, writing and observing cookies, and what stays exactly the same?

level: seniorimportance: nice to knowfreq 27%

answer

  1. promise-based, not a string
  2. objects carry the attributes
  3. works inside a service worker
  4. change event replaces polling
  5. HttpOnly blindness is unchanged

basics

~20 s

cookieStore is a promise-based API returning cookie objects with their attributes, usable in service workers, with a change event instead of polling. The security model is unchanged: HttpOnly cookies stay invisible, and it is secure-context only.

solid answer

~50 s

`cookieStore` replaces string parsing with structured, asynchronous access: `await cookieStore.get('theme')` resolves to an object carrying `name`, `value`, `domain`, `path`, `expires`, `secure` and `sameSite`, `getAll()` returns all of them, and `set({ name, value, path, expires })` and `delete('theme')` return promises that actually reject when the write is refused — the silent failure of `document.cookie` is gone. It also fixes two structural gaps: it is available inside service workers, where `document` does not exist, and it emits a `change` event with `changed` and `deleted` arrays so code can react to a cookie being written elsewhere instead of polling. What does not change is the security model — `HttpOnly` cookies are excluded exactly as before, scoping is still name plus domain plus path, and the API is restricted to secure contexts. Support is still uneven, so in production I feature-detect `window.cookieStore` and keep a `document.cookie` fallback.

code

javascript · 18 lines
javascript
if ('cookieStore' in globalThis) {
  const c = await cookieStore.get('theme');
  console.log(c?.value, c?.path, c?.expires); // attributes are visible here

  await cookieStore.set({
    name: 'theme',
    value: 'dark',
    path: '/',
    expires: Date.now() + 86_400_000,
    sameSite: 'lax',
  });

  cookieStore.addEventListener('change', (e) => {
    console.log(e.changed.map((c) => c.name), e.deleted.map((c) => c.name));
  });

  await cookieStore.delete({ name: 'theme', path: '/' });
}

go deeper

for a junior

Know that cookieStore is the promise-based alternative to parsing document.cookie, and that it returns cookie objects rather than one long string.

for a middle

Explain the concrete gains — attributes visible on reads, promises that reject on refused writes, availability in service workers, a change event instead of polling.

for a senior

Show adoption judgment: feature-detect behind a façade, keep the synchronous path for values needed before first paint, and state plainly that the HttpOnly and scoping rules are untouched.

for a principal

Own the migration stance — whether a new interface with uneven support earns a place in the codebase, and how you keep one cookie abstraction rather than two competing idioms across teams.

## The three problems it addresses `document.cookie` has three structural defects that no amount of helper code fixes: it is a **string** you must parse, it is **synchronous** on the main thread, and it exists only on `document`, so a service worker cannot use it. The `cookieStore` interface, exposed as `window.cookieStore` in a document and `self.cookieStore` in a service worker, addresses all three. ## Structured reads ```js const c = await cookieStore.get('theme'); // { name: 'theme', value: 'dark', domain: 'example.com', // path: '/', expires: 1793827200000, secure: true, sameSite: 'lax' } const all = await cookieStore.getAll(); ``` The returned object carries the **attributes**, which is the single biggest functional gain: `document.cookie` can never tell you a cookie's path or expiry, so the delete-with-the-wrong-path bug is unavoidable there and avoidable here. `get()` resolves to `null` when there is no match rather than making you scan a string, and no `decodeURIComponent` dance is required for the pair itself. ## Writes that report failure ```js try { await cookieStore.set({ name: 'theme', value: 'dark', path: '/', expires: Date.now() + 86_400_000, // ms since epoch, not an HTTP date sameSite: 'lax', }); } catch (err) { // rejects when the browser refuses the write } await cookieStore.delete('theme'); // or delete({ name, path, domain }) ``` `set()` takes an options object — or the shorthand `set(name, value)` — and returns a promise. A refused write **rejects** instead of vanishing, which is the reliability difference that matters most in real code. `expires` is a timestamp in milliseconds, so no HTTP-date formatting. `delete()` still expires the cookie by name plus scope, and you still supply `path`/`domain` when the cookie was not written at the defaults. ## Observation instead of polling ```js cookieStore.addEventListener('change', (event) => { for (const c of event.changed) console.log('written', c.name, c.value); for (const c of event.deleted) console.log('removed', c.name); }); ``` With `document.cookie` there is no notification of any kind, so code that needs to react to a cookie written by another tab, by a `Set-Cookie` response, or by devtools has to poll on a timer. The `change` event ends that. In a service worker the equivalent is a `cookiechange` event on the global scope, which is how a worker can react to a session cookie disappearing. ## What is deliberately unchanged This is the half candidates forget, and the half interviewers are checking: - **`HttpOnly` still wins.** `cookieStore.get()` and `getAll()` exclude those cookies exactly as `document.cookie` does. The API is a better interface to the same store, not a privilege escalation. - **Scoping is unchanged.** A cookie is still identified by name plus domain plus path; the same delete-the-wrong-one hazard exists, only now you can *see* the path and get it right. - **The wire behaviour is unchanged.** Cookies still ride along on matching requests with the same size budgets and per-domain caps. - **Secure context only.** The API is not exposed on plain `http:` pages, so a fallback path is mandatory anyway for any non-HTTPS environment. ## Adopting it safely Support is genuinely uneven — it shipped in Chromium browsers (Chrome and Edge 87) and is not available everywhere, so the honest production pattern is a thin façade with feature detection: ```js const cookies = 'cookieStore' in globalThis ? { get: (n) => cookieStore.get(n).then((c) => c?.value) } : { get: async (n) => readFromDocumentCookie(n) }; ``` One more practical asymmetry: because `cookieStore` is asynchronous, a cookie you must have available before the first paint — a theme flag driving a class on `<html>`, for instance — is still better read synchronously from `document.cookie` in a blocking inline script. Async is the right default, but "needed before the first frame" is a real exception. ## The one-line summary `cookieStore` fixes the *interface* — structured, async, observable, worker-available, failure-reporting — while leaving the *policy* untouched: the same cookies, the same scoping rules, and the same HttpOnly blind spot.

  • Which real bug does `cookieStore` make impossible that `document.cookie` cannot?
    Deleting the wrong cookie because you guessed its path. `document.cookie` never returns attributes, so two cookies of the same name at `/` and `/app` are indistinguishable and a delete write may target neither. `cookieStore.getAll()` returns each one with its `path` and `domain`, so you can find the exact cookie and pass those values to `delete()`.
  • Is there any reason to keep reading `document.cookie` in new code?
    Two. Support is not universal, so a feature-detected fallback is still needed on browsers without `cookieStore`, and on plain `http:` where the secure-context requirement excludes it. And it is synchronous: a value needed before the first paint, such as a theme class applied by a blocking inline script, cannot wait on a promise without risking a flash of the wrong state.
  • Can a service worker use `cookieStore` to watch for the session cookie disappearing?
    Yes for non-HttpOnly cookies — `self.cookieStore` is available in a service worker, and the global fires a `cookiechange` event carrying `changed` and `deleted` lists. But a session cookie is normally `HttpOnly`, and that filter applies in the worker too, so the worker cannot observe the one cookie you would most want to watch.

saying these in an interview costs you the question

  • Thinks cookieStore can read HttpOnly cookies
  • Assumes it is available in every browser today
  • Expects synchronous access and blocks first paint on it
  • Passes an HTTP date string to the expires option
  • Believes it changes how cookies are sent on requests

context