⚡ NEW: Gallery - Full Gallery 2025
Cookie Store API
Living Standard — Last Updated
- Participate:
- GitHub whatwg/cookiestore (new issue, open issues)
- Chat on Matrix
- Commits:
- GitHub whatwg/cookiestore/commits
- Snapshot as of this commit
- @cookiestoreapi
- Snapshot as of this commit
- Tests:
- web-platform-tests cookiestore/ (ongoing work)
- Translations (non-normative):
- 简体ä¸ć–‡
- 日本語
- 한ęµě–´
- 日本語
Abstract
An asynchronous JavaScript cookies API for documents and service workers.
1. Introduction
This section is non-normative.
This standard defines an asynchronous cookie API for scripts running in HTML documents and service workers.
HTTP cookies have, since their origins at Netscape (documentation preserved by archive.org), provided a valuable state-management mechanism for the web.
The synchronous single-threaded script-level document.cookie interface to cookies has been a source of complexity and performance woes further exacerbated by the move in many browsers from:
-
a single browser process,
-
a single-threaded event loop model, and
-
no general expectation of responsiveness for scripted event handling while processing cookie operations
… to the modern web which strives for smoothly responsive high performance:
-
in multiple browser processes,
-
with a multithreaded, multiple-event loop model, and
-
with an expectation of responsiveness on human-reflex time scales.
On the modern web a cookie operation in one part of a web application cannot block:
-
the rest of the web application,
-
the rest of the web origin, or
-
the browser as a whole.
Newer parts of the web built in service workers need access to cookies too but cannot use the synchronous, blocking document.cookie interface at all as they both have no document and also cannot block the event loop as that would interfere with handling of unrelated events.
1.1. Alternative to document.cookie
Today writing a cookie means blocking your event loop while waiting for the browser to synchronously update the cookie jar with a carefully-crafted cookie string in Set-Cookie format:
document. cookie= '__Secure-COOKIENAME=cookie-value' + '; Path=/' + '; expires=Fri, 12 Aug 2016 23:05:17 GMT' + '; Secure' + '; Domain=example.org' ; // now we could assume the write succeeded, but since // failure is silent it is difficult to tell, so we // read to see whether the write succeeded var successRegExp= /(^|; ?)__Secure-COOKIENAME=cookie-value(;|$)/ ; if ( String( document. cookie). match( successRegExp)) { console. log( 'It worked!' ); } else { console. error( 'It did not work, and we do not know why' ); }
What if you could instead write:
const one_day_ms= 24 * 60 * 60 * 1000 ; cookieStore. set( { name: '__Secure-COOKIENAME' , value: 'cookie-value' , expires: Date. now() + one_day_ms, domain: 'example.org' }). then( function () { console. log( 'It worked!' ); }, function ( reason) { console. error( 'It did not work, and this is why:' , reason); }); // Meanwhile we can do other things while waiting for // the cookie store to process the write...
This also has the advantage of not relying on document and not blocking, which together make it usable from service workers, which otherwise do not have cookie access from script.
This standard also includes a power-efficient monitoring API to replace setTimeout-based polling cookie monitors with cookie change observers.
1.2. Summary
In short, this API offers the following functionality:
-
write (or "set") and delete (or "expire") cookies
-
read (or "get") script-visible cookies
-
... including for specified in-scope request paths in service worker contexts
-
-
monitor script-visible cookies for changes using
CookieChangeEvent-
... in long-running script contexts (e.g.
document) -
... for script-supplied in-scope request paths in service worker contexts
-
1.3. Querying cookies
Both documents and service workers access the same query API, via the
cookieStore property on the global object.
The get() and getAll() methods on CookieStore are used to query cookies.
Both methods return Promises.
Both methods take the same arguments, which can be either:
-
a name, or
-
a dictionary of options (optional for
getAll())
The get() method is essentially a form of getAll() that only returns the first result.
try { const cookie= await cookieStore. get( 'session_id' ); if ( cookie) { console. log( `Found ${ cookie. name} cookie: ${ cookie. value} ` ); } else { console. log( 'Cookie not found' ); } } catch ( e) { console. error( `Cookie store error: ${ e} ` ); }
try { const cookies= await cookieStore. getAll( 'session_id' }); for ( const cookieof cookies) console. log( `Result: ${ cookie. name} = ${ cookie. value} ` ); } catch ( e) { console. error( `Cookie store error: ${ e} ` ); }
Service workers can obtain the list of cookies that would be sent by a fetch to any URL under their scope.
await cookieStore. getAll({ url: '/admin' });
Documents can only obtain the cookies at their current URL. In other words,
the only valid url value in Document contexts is the document’s URL.
The objects returned by get() and getAll() contain all the relevant information in the cookie store, not just the name and the value as in the older document.cookie API.
await cookie= cookieStore. get( 'session_id' ); console. log( `Cookie scope - Domain: ${ cookie. domain} Path: ${ cookie. path} ` ); if ( cookie. expires=== null ) { console. log( 'Cookie expires at the end of the session' ); } else { console. log( `Cookie expires at: ${ cookie. expires} ` ); } if ( cookie. secure) console. log( 'The cookie is restricted to secure origins' );
1.4. Modifying cookies
Both documents and service workers access the same modification API, via the
cookieStore property on the global object.
Cookies are created or modified (written) using the set() method.
try { await cookieStore. set( 'opted_out' , '1' ); } catch ( e) { console. error( `Failed to set cookie: ${ e} ` ); }
The set() call above is shorthand for using an options dictionary, as follows:
await cookieStore. set({ name: 'opted_out' , value: '1' , expires: null , // session cookie // By default, domain is set to null which means the scope is locked at the current domain. domain: null , path: '/' });
Cookies are deleted (expired) using the delete() method.
try { await cookieStore. delete ( 'session_id' ); } catch ( e) { console. error( `Failed to delete cookie: ${ e} ` ); }
Under the hood, deleting a cookie is done by changing the cookie’s expiration date to the past, which still works.
try { const one_day_ms= 24 * 60 * 60 * 1000 ; await cookieStore. set({ name: 'session_id' , value: 'value will be ignored' , expires: Date. now() - one_day_ms}); } catch ( e) { console. error( `Failed to delete cookie: ${ e} ` ); }
1.5. Monitoring cookies
To avoid polling, it is possible to observe changes to cookies.
In documents, change events are fired for all relevant cookie changes.
change events in documents:
cookieStore. addEventListener( 'change' , event=> { console. log( ` ${ event. changed. length} changed cookies` ); for ( const cookiein event. changed) console. log( `Cookie ${ cookie. name} changed to ${ cookie. value} ` ); console. log( ` ${ event. deleted. length} deleted cookies` ); for ( const cookiein event. deleted) console. log( `Cookie ${ cookie. name} deleted` ); });
In service workers, cookiechange events are fired against the global scope, but an explicit subscription is required, associated with the service worker’s registration.
cookiechange events in a service worker:
self. addEventListener( 'activate' , ( event) => { event. waitUntil( async () => { // Snapshot current state of subscriptions. const subscriptions= await self. registration. cookies. getSubscriptions(); // Clear any existing subscriptions. await self. registration. cookies. unsubscribe( subscriptions); await self. registration. cookies. subscribe([ { name: 'session_id' , // Get change events for cookies named session_id. } ]); }); }); self. addEventListener( 'cookiechange' , event=> { // The event has |changed| and |deleted| properties with // the same semantics as the Document events. console. log( ` ${ event. changed. length} changed cookies` ); console. log( ` ${ event. deleted. length} deleted cookies` ); });
Calls to subscribe() are cumulative, so that independently maintained
modules or libraries can set up their own subscriptions. As expected, a service worker’s
subscriptions are persisted for with the service worker registration.
Subscriptions can use the same options as get() and getAll().
The complexity of fine-grained subscriptions is justified
by the cost of dispatching an irrelevant cookie change event to a service worker,
which is much higher than the cost of dispatching an equivalent event
to a Window. Specifically, dispatching an event to a service worker might
require waking up the worker, which has a significant impact on battery life.
The getSubscriptions() allows a service worker to introspect
the subscriptions that have been made.
const subscriptions= await self. registration. cookies. getSubscriptions(); for ( const subof subscriptions) { console. log( sub. name, sub. url); }
2. Concepts
2.1. Cookie
A cookie is normatively defined for user agents by Cookies § User Agent Requirements.
To normalize a cookie name or value given a string input: remove all U+0009 TAB and U+0020 SPACE that are at the start or end of input.
A cookie is script-visible when it is in-scope and its http-only-flag is unset. This is more formally enforced in the processing model, which consults Cookies § Retrieval Model at appropriate points.
A cookie is also subject to certain size limits. Per Cookies § Storage Model:
-
The combined lengths of the name and value fields must not be greater than 4096 bytes (the maximum name/value pair size).
-
The length of every field except the name and value fields must not be greater than 1024 bytes (the maximum attribute value size).
Cookie attribute-values are stored as byte sequences, not strings.
2.2. Cookie store
A cookie store is normatively defined for user agents by Cookies § User Agent Requirements.
When any of the following conditions occur for a cookie store, perform the steps to process cookie changes.
-
A newly-created cookie is inserted into the cookie store.
-
A user agent evicts expired cookies from the cookie store.
-
A user agent removes excess cookies from the cookie store.
2.3. Extensions to Service Workers
[Service-Workers] defines service worker registration, which this specification extends.
A service worker registration has an associated cookie change subscription list which is a list; each member is a cookie change subscription. A cookie change subscription is a tuple of name and url.
3. The CookieStore interface
[Exposed =(ServiceWorker ,Window ),SecureContext ]interface :CookieStore EventTarget {Promise <CookieListItem ?>get (USVString );name Promise <CookieListItem ?>get (optional CookieStoreGetOptions = {});options Promise <CookieList >getAll (USVString );name Promise <CookieList >getAll (optional CookieStoreGetOptions = {});options Promise <undefined >set (USVString ,name USVString );value Promise <undefined >set (CookieInit );options Promise <undefined >delete (USVString );name Promise <undefined >delete (CookieStoreDeleteOptions ); [options Exposed =Window ]attribute EventHandler ; };onchange dictionary {CookieStoreGetOptions USVString ;name USVString ; };url enum {CookieSameSite ,"strict" ,"lax" };"none" dictionary {CookieInit required USVString ;name required USVString ;value DOMHighResTimeStamp ?=expires null ;USVString ?=domain null ;USVString = "/";path CookieSameSite = "strict";sameSite boolean =partitioned false ;long long ?=maxAge null ; };dictionary {CookieStoreDeleteOptions required USVString ;name USVString ?=domain null ;USVString = "/";path boolean =partitioned false ; };dictionary {CookieListItem USVString ;name USVString ; };value typedef sequence <CookieListItem >;CookieList
3.1. The get() method
- cookie = await cookieStore .
get(name)- cookie = await cookieStore .
get(options) - cookie = await cookieStore .
-
Returns a promise resolving to the first in-scope script-visible value for a given cookie name (or other options). In a service worker context this defaults to the path of the service worker’s registered scope. In a document it defaults to the path of the current document and does not respect changes from
replaceState()ordocument.domain.
get(name) method steps are:
-
Let settings be this’s relevant settings object.
-
Let origin be settings’s origin.
-
If origin is an opaque origin, then return a promise rejected with a "
SecurityError"DOMException. -
Let url be settings’s creation URL.
-
Let p be a new promise.
-
Run the following steps in parallel:
-
Return p.
get(options) method steps are:
-
Let settings be this’s relevant settings object.
-
Let origin be settings’s origin.
-
If origin is an opaque origin, then return a promise rejected with a "
SecurityError"DOMException. -
Let url be settings’s creation URL.
-
If options is empty, then return a promise rejected with a
TypeError. -
-
Let parsed be the result of parsing options["
url"] with settings’s API base URL. -
If this’s relevant global object is a
Windowobject and parsed does not equal url with exclude fragments set to true, then return a promise rejected with aTypeError. -
If parsed’s origin and url’s origin are not the same origin, then return a promise rejected with a
TypeError. -
Set url to parsed.
-
-
Let p be a new promise.
-
Run the following steps in parallel:
-
Let list be the results of running query cookies with url and options["
name"] with default null. -
If list is failure, then reject p with a
TypeErrorand abort these steps. -
Otherwise, resolve p with the first item of list.
-