Cookies are one of the oldest ways to store information in the browser. They were designed to be a reliable mechanism of storing stateful or session information for the stateless HTTP protocol. This kind of information generally comprises an authenticated user’s browsing activity or behavior, such as the pages they have visited or the links they have clicked.
This is how cookies work in a nutshell: after receiving an HTTP request, a server can send one or more Set-Cookie
headers with the response back to the client. The cookie is usually stored by this client, and it can then be sent with requests made to the same server inside a Cookie
HTTP header. Therefore, cookies are used to tell whether requests came from the same browser client session.
While cookies solved an important use case, they posed lots of problems. Their interface was quite complicated, as saving all of the cookies in document.cookie
— which is part of the DOM — was problematic. There was no way of telling the result of performing a write. The document script needs to issue a read or get request to verify the result of a previous write or create request.
Another problem is that when the attributes of a cookie are read, they still need to be parsed/serialized. This is because they are all returned as a single string, with each cookie’s name-value pair concatenated into a list of pairs, and each list item separated by a semicolon.
This posed another challenge when generally managing cookies or performing actions like getting a cookie, checking for the existence of a cookie, checking for the existence of a particular value in the cookie data, and so on.
These days, developers are strongly encouraged to use modern storage APIs like IndexedDB or localStorage
for storing session data in the browser. This is because they have larger storage limits than cookies, and cookie information from the client is never sent to the server.
If you still have a strong case for using cookies in your projects, however, you’re in luck: the async Cookie Store API presents a new and improved way of doing things.
Here, we will explore the behavior and complexities associated with cookies so that we can begin to appreciate the usefulness of the new async API.
While cookies are widely used today, their interface has been a source of complexity and performance issues. The document.cookie
property lets us read and write cookies associated with the document. This document serves as a getter and setter for the actual values of the cookies.
However, whenever we use the document.cookie
getter, the browser has to stop executing JavaScript until it has the cookie information we requested. Of course, this can lead to issues with the UI/UX. Let’s explore the read and write cookie operations below, so we can get a better feel.
Getting a specific cookie value always seemed like a very difficult thing to do. We had an option of iterating over the document.cookie
value, which is a full string containing all of the cookies. Let’s see an example of accessing a cookie value with a particular name:
document.cookie = "name1=value1"; document.cookie = "name2=value2"; document.cookie = "name3=value3"; // to get a cookie value with name value2 const cookieValue = document.cookie .split('; ') .find(row => row.startsWith('name2')) .split('=')[1]; console.log(cookieValue) // value2
A more generic way of doing this, via this enduring Stack Overflow thread, would be:
const getCookie = (name) => { return document.cookie.split('; ').reduce((r, v) => { const parts = v.split('=') return parts[0] === name ? decodeURIComponent(parts[1]) : r }, '') }
The API for setting a cookie value seems so old-fashioned — why? Well, after setting cookie data, there is no way for us to know that the cookie was successfully created.
A workaround is to use a getCookie
function, like the one above, to loop over the cookie string to find the values we set for the cookie data.
document.cookie = "name=value";
Another issue when setting cookies is that there are no well-defined mechanisms for reporting cookie storage errors. This, of course, is due to document.cookie
being synchronous.
Tired of the weird ways of getting cookies from document.cookie
? Unsure whether the cookie you set was actually created? This section addresses those concerns.
The new Cookie Store API aims to improve all the shortcomings of working with cookies by providing an asynchronous alternative to document.cookie
and exposing these cookies to service workers. The API provides a robust and logical method for cookie management. In summary, the API makes it easier to:
Note: Due to the synchronous design of the
document.cookie
API, cookies were formerly inaccessible to the scope of service workers.
Service workers need to be able to read and modify the cookies accessible to pages under their scope since they are intended to act as HTTP proxies of some sort. Also, they need to quickly react to changes in session state, which will help in the clean up of old or stale cached data.
The new Cookie Store API contains methods for observing cookie changes (instead of polling) in documents and service workers. The methods for observing cookie changes include the ability to have a service worker activated when a cookie changes its value.
To make use of the Cookie Store API today, we can enable the origin trial flag. At the time of writing, however, the origin trial flag is closed as the API is currently undergoing major improvements based on community feedback. The origin trial is expected to be reopened after the maintenance and improvements are complete. You can find more details here.
With that said, you can still try it out locally — the API can be enabled on the CLI, which enables the API globally in Chrome for the current session. See the command below:
chrome --enable-blink-features=CookieStore
Alternatively, we can enable the #enable-experimental-web-platform-features
flag in chrome://flags
.
Both document window 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. Don’t forget that this returns a promise, which allows us to check for errors easily.
They take the same arguments, which can be:
getAll()
)The get()
method is essentially a form of getAll()
that only returns the first result. Here’s an example:
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 (error) { console.error(`Cookie store error: ${error}`); }
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.
Also, 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 (error) { console.error(`Failed to set cookie: ${error}`); }
Keep in mind that the change is only guaranteed to be applied after the promise returned by cookieStore.set
resolves.
Cookies are deleted (expired) using the delete()
method.
try { await cookieStore.delete('session_id'); } catch (error) { console.error(`Failed to delete cookie: ${error}`); }
Under the hood, deleting a cookie is done by changing the cookie’s expiration date to the past, which still works.
The Cookie Store API brings an alternative method for observing cookie changes, which does not require polling. A popular application for accessing cookies from JavaScript is detecting when the user logs out, and updating the UI accordingly.
Change events are fired for all relevant cookie changes. A simple example to register for change
events is shown below –
cookieStore.addEventListener('change', event => { console.log(`${event.changed.length} changed cookies`); for (const cookie in event.changed) console.log(`Cookie ${cookie.name} changed to ${cookie.value}`); for (const cookie in event.deleted) console.log(`Cookie ${cookie.name} deleted`); });
This API is also designed to allow browsers to batch change events for performance reasons. More details can be found here.
Service workers needing access to cookies cannot rely on the synchronous, blocking the document.cookie
interface. This is because service workers cannot block the event loop, as that would interfere with the handling of other events.
However, the Cookie Store API is asynchronous, and therefore is allowed in service workers. Note that service workers might sometimes need cookie access, for instance:
Interacting with the cookies works the same way in both document contexts and in service workers. However, observing cookie changes is a bit different in service workers. This is because waking up a service worker can be pretty expensive; thus, there needs to be an explicit description of the cookie changes that the worker is interested in.
More details can be found in the draft document here. Also, we can refer to the section on change events in service workers in the explainer doc.
This exciting proposal provides an asynchronous cookie API for the following document events: set, delete, and read operations. Perhaps most importantly, it will allow service workers to read cookies, which isn’t possible today; reading and writing cookies from the document is currently a synchronous process, causing slow and annoying page load times. Asynchronous access to cookies addresses these needs.
The API includes a cookiechange
event that will wake up service workers. Cookie change events in service workers are fired against the global scope, but an explicit subscription is required, associated with the service worker’s registration. More details here.
The API will also have a well-defined mechanism for reporting cookie storage errors. It also solves some of the known cross-browser incompatibilities and differences between specification and browser behavior.
Currently, browsers need to include a snapshot of cookies in every HTTP request, leading to cookie changes being propagated across the storage and network stacks. Modern browsers have highly optimized cookie store implementations, but we’ll never be able to make cookies as efficient as the other storage mechanisms, which don’t need to talk to the network stack. This proposal however, does not aim to change how cookies are handled at the network layer or the security model of cookies in general
For more detailed information about the Cookie Store API, you can check the proposal document/draft here, the explainer material, and also the GitHub repo. Details about the cookie change event can be found here. Furthermore, you can also compare this proposal with an alternative implementation, a simple, lightweight JavaScript API for handling browser cookies.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.