JavaScript CookieStore Table

PieceWhat it doesField note
cookieStore.get(name)The promise readresolves a cookie object (value, expires, sameSite, partitioned) or null - no string splitting, metadata included
cookieStore.set(obj)The structured writename, value, expires, path, sameSite as fields; rejects on real failures instead of silently corrupting the jar
cookieStore.delete(name)Scoped deletionpath and domain must match the write that created the cookie - the classic deleted-but-came-back bug
cookieStore.onchangeThe change feedchanged and deleted arrays with oldValues, across tabs, iframes and the service worker; fires for your own writes too
cookieStore in workersThe SW accessdocument.cookie does not exist in service workers - cookieStore is the only sanctioned cookie read there
partitioned cookies (CHIPS)The partition keyidentity becomes (name, domain, path, top-level site); embedded services get a separate jar per embedder
sameSite: lax / strict / noneThe request scopegoverns cross-site sending, unchanged by the API - cookieStore reads what the jar holds, SameSite still rules the wire
the 4KB ceilingThe unchanged realitystill a small string store echoed on every request - sessions and flags, never data; IndexedDB remains the database
Reference: the MDN CookieStore. The structured face of cookies: promises instead of string splicing, real metadata on every read, and a change event that turns session handling from polling into reaction - in pages and in service workers alike.
Bottom line: feature-detect (Chromium-only) and write a small adapter so call sites stay clean; guard your own onchange echo; delete with the exact path and domain the cookie was written with; and remember nothing here rehabilitates cookies as storage - 4KB per cookie, echoed on every request. Sessions, consent flags and themes; everything else belongs in IndexedDB.
Related tools: the localStorage table (the other small store, no server echo), the service worker table (where cookieStore is irreplaceable), the storage manager table (quota and persistence for real data), the storage access table (embedded identity under partitioning), and the fetch table (where Set-Cookie actually arrives).

The Cookie Store API is the modern replacement for document.cookie - the last major platform API still stuck in 1990s string-splicing. Instead of one serialized string you parse and reassemble blind, cookieStore gives you first-class cookie objects: await cookieStore.get(name) returns a promise of {name, value, domain, path, expires, secure, sameSite}, set() takes an object or named arguments, and - the part that changes architectures - a change event tells you when cookies actually change, in the page or in a service worker. document.cookie never told you anything unless you polled it; cookieStore pushes.

Bottom line: read with get/getAll, write with set, delete with delete - all promises, all structured. The two properties that make it worth migrating: it works in service workers (document.cookie simply does not exist there, which is why SW-era code resorted to header hacks), and the change event (cookieStore.onchange) delivers changed/deleted cookie records with their old and new values, so session-expiry handling and multi-tab coordination stop being polling loops. Security posture is inherited from the platform: secure context only, same-origin writes only - you can only touch cookies your origin owns, no third-party cookie manipulation from script.

The integration reality: cookieStore is Chromium-only and additive - document.cookie keeps working, so adoption is per-feature, not a big-bang migration. The footguns that remain are cookie footguns, not API footguns: the 4KB-per-cookie and ~50-cookies-per-domain ceilings, path and domain scoping that makes get() return whichever cookie matches first, SameSite defaults that keep cookies out of cross-site requests, and the arrival of partitioned cookies (CHIPS) which adds a partition key to the identity. Use cookieStore where it shines - SW-side session checks, change-driven UI, cleaner auth code - and keep expectations cookie-shaped: it is still a 4KB string store with a better API, not a database.

How to use

  1. Feature-detect and read: if (!('cookieStore' in window)) fall back to document.cookie; const c = await cookieStore.get('session') returns the cookie object or null (get resolves null, it does not reject, for absent cookies).
  2. Write with intent: await cookieStore.set({name: 'theme', value: 'dark', expires: Date.now() + 7*864e5, sameSite: 'lax'}) - expires defaults to session (browser close); set() rejects only on real failures (invalid name, secure-context violation), not on quota silence.
  3. Listen for changes: cookieStore.onchange = e => e.changed.forEach(c => sync(c.name, c.value)) and e.deleted carries oldValue - fires for your own writes too, so guard against echo if you both write and listen.
  4. Use it in the service worker: self.registration + cookieStore.get('session') inside SW code - the one place the API is irreplaceable, since document.cookie is undefined there; validate session cookies on fetch() interception instead of trusting cached state.
  5. Delete explicitly and completely: await cookieStore.delete({name: 'session', path: '/'}) - deletion is path/domain-scoped like writes; a cookie set with a path needs the same path to disappear, which is why 'I deleted it but it came back' happens.

Frequently asked questions

Why migrate off document.cookie - what does the promise API actually fix?

Four concrete fixes, in descending order of architectural weight. First, workers: document.cookie exists only on Document - service workers had no cookie access at all, so session-aware SW code either proxied to a window client or reimplemented session logic from request headers; cookieStore runs in both contexts and is the only sanctioned cookie read in a SW. Second, the parse tax: document.cookie returns 'a=1; b=2; c=3' - a string you split, decode and filter yourself, with no metadata (expiry, sameSite, partition are invisible); cookieStore.get returns a parsed object with the metadata, and decoding is handled. Third, change visibility: the old pattern for reacting to cookie changes was setInterval polling or catching HTTP responses; onchange fires exactly when cookies change, with changed and deleted arrays including oldValues - session expiry, logout-in-another-tab, and server Set-Cookie responses all surface as events instead of surprises on the next poll. Fourth, write safety: document.cookie writes are string-merge operations where a stray semicolon in a value silently corrupts neighbors and deleting means overwriting with a past expiry date in the exact right domain/path; set/delete are structured operations with real rejections. What it does not fix: cookies are still per-domain 4KB strings sent on every matching request, still the wrong tool for anything over a few values, still visible to the server on every fetch - IndexedDB remains the store for data; cookieStore just makes the cookie you genuinely need (a session id, a consent flag) pleasant to use.

How does the change event behave across tabs, iframes and the service worker?

onchange fires in every context that shares the cookie's identity scope: other tabs of the same origin, same-origin iframes (subject to storage partitioning - a partitioned iframe sees its own partition's changes), and the service worker. The event carries two arrays: changed (cookies created or overwritten - each with name, value and the full metadata) and deleted (cookies removed or expired - each with oldValue attached). Three behaviors to internalize. Echo: your own set() triggers your own onchange handler asynchronously - if a handler both writes and reacts, guard with a flag or compare values to avoid loops; this is the most common first-week bug. Scope: the event fires only for cookies your context can see (same origin, matching path - a cookie set with path=/app fires in pages under /app); a document.cookie write from a legacy code path still fires the event, which makes cookieStore.onchange the perfect adapter while you migrate writers gradually. Timing: events are delivered as a task after the change, so coalesce rapid changes (a login burst may deliver several events) rather than assuming one-event-per-cookie-write. In the SW, onchange is how you invalidate cached authorization: session cookie changed -> flush the cached fetch wrapper's auth state. Cross-site changes never fire - a third-party cookie change on someone else's site is invisible to you, by design; and with third-party cookies being phased out, cross-site cookie events are a shrinking world anyway.

What are partitioned cookies and how do they interact with cookieStore?

Partitioning (CHIPS - Cookies Having Independent Partitioned State) changes cookie identity from (name, domain, path) to (name, domain, path, partition key), where the partition key is the top-level site the user is actually visiting. The problem it solves: an embedded third party (support chat, payment iframe) could previously set a cookie that identified the user across every site embedding it - the tracking cookie in its purest form. With partitioned cookies, the same embedded service gets a separate cookie jar per top-level site: the chat widget's session on shop-a.example and on shop-b.example are unrelated cookies. In cookieStore, a partitioned cookie appears with partitioned: true and the API handles the keying implicitly - you set {name, value, partitioned: true} (secure is implied and required) and reads from within the iframe land in the right partition automatically; there is no partition key parameter for script to juggle. The practical consequences: first, embedded services need code changes - a service that relied on recognizing a user across sites loses that ability, deliberately, and must migrate to explicit identity (a first-party handshake with the embedder); second, session checks in embedded iframes should read partitioned cookies via cookieStore rather than reaching for legacy identity; third, debugging changes - DevTools shows the partition key per cookie, and 'my cookie is not visible' in an iframe is most often a partition mismatch, not a name typo. The Server Side note: Set-Cookie needs the Partitioned attribute from the server; client-side cookieStore.set with partitioned on a top-level page is a no-op distinction, the partition concept only means something in an embedded context.

Where does CookieStore still fall short, and what is the honest fallback story?

The gaps, in order of how often they bite. Browser coverage: Chromium-only - Firefox and Safari expose document.cookie only, so every cookieStore call site needs the feature-detect plus a fallback path; write the fallback as a small adapter (get/set/delete/onchange-shaped shim over document.cookie, minus change events which become a 1-second poll or nothing) and keep call sites clean. No registration-time semantics: onchange is a live event - there is no way to replay history or get changes while the page is closed; SW context keeps listeners alive only while the SW stays resident, so expiry-driven wakeups are not a thing (that is what push and background sync are for). Quota realities unchanged: 4KB per cookie, dozens per domain, everything echoed to the server on every request - storing a shopping cart in cookies remains an antipattern that cookieStore does not rehabilitate. Path/domain archaeology: get('session') can return a cookie set at a narrower path shadowing the one you meant - getAll and inspecting path/domain on results is the defense, and delete-scope mismatch is still the classic 'it came back' bug. Write rejection opacity: quota exhaustion on cookie writes is historically silent in the platform (the Set-Cookie just does not happen); cookieStore.set surfaces some failures as rejections but do not treat a resolved promise as proof the cookie persists - verify critical cookies with a read-back. The decision rule: session id, CSRF token, consent flag, theme - cookieStore territory, cleaner than the string API has any right to be; user preferences that fit in a kilobyte and must reach the server anyway - fine; anything else - IndexedDB, and use cookies only for the handle.

Related tools