← blog
JavaScriptSeptember 4, 2026 · 5 min read

Stop Regex-Parsing document.cookie. Use CookieStore

document.cookie hands you one flat string and no way to know when it changes. The Cookie Store API gives you async get/set/delete and a change event — but its set() defaults to a stricter SameSite than the one you're used to.

Parsa Jiravand · Frontend engineer · building bestpractic
Stop Regex-Parsing document.cookie. Use CookieStore

Open any codebase old enough to have cookies in it and grep for document.cookie. You will find a function that looks like this, written by someone who is no longer at the company:

JavaScript
1
2
3
4
5
6
function getCookie(name) { const match = document.cookie.match( new RegExp("(^| )" + name + "=([^;]+)") ); return match ? decodeURIComponent(match[2]) : null; }

It works. It has worked since 2011. It also has a small, well-known list of ways to get it wrong — cookie names that are prefixes of each other, values with unescaped = or ;, whitespace after the semicolon depending on which browser wrote the header. Everyone's seen at least one of these bugs. Nobody rewrites the function, because it's not broken today.

Here's the harder problem that regex can't fix at all: you have no way to know when a cookie changes. Not from another tab. Not from a Set-Cookie header on a fetch() response. Not even from a second script on your own page calling document.cookie = ... a moment after yours did. document.cookie is a plain string property. Reading it tells you the current state. It has never told you when the state moved.

Once the "I need to react to cookie changes" requirement shows up — a login cookie set by an API call, a consent banner another tab just dismissed — the usual fixes are:

  • Poll document.cookie on an interval. It works, in the sense that a setInterval checking a string every 500ms will eventually notice a change. It also means every tab of every user is now diffing a string forever for an event that might happen once a session.
  • Have the server tell the client via a WebSocket or SSE. Real infrastructure for a problem that's purely local — the cookie already changed on this machine, in this browser, you just don't have a hook for it.
  • Wrap every single place that writes a cookie in your own pub/sub. This can work, right up until a third-party script, a Set-Cookie response header, or literally the browser's own cookie-jar expiry logic changes a cookie your pub/sub doesn't know about.

All three treat "the browser won't tell me" as something to engineer around. It's worth asking why the browser won't tell you in the first place — and it turns out, more recently, it will.

Chrome and Edge ship window.cookieStore (and self.cookieStore inside a service worker) — a promise-based Cookie Store API that treats cookies as structured objects instead of one string you serialize by hand.

Reading is no longer a regex:

JavaScript
1
2
3
4
5
const session = await cookieStore.get("session_id"); // { name: "session_id", value: "abc123", domain: null, path: "/", ... } or null const all = await cookieStore.getAll(); // array of every cookie visible to this document, already parsed

Writing takes an object instead of a hand-built key=value; path=...; expires=... string:

JavaScript
1
2
3
4
5
6
7
8
await cookieStore.set({ name: "theme", value: "dark", expires: Date.now() + 1000 * 60 * 60 * 24 * 30, // 30 days, in ms path: "/", }); await cookieStore.delete("theme");

And the part document.cookie could never do — a real event:

JavaScript
1
2
3
4
5
6
7
8
cookieStore.addEventListener("change", (event) => { for (const cookie of event.changed) { console.log("set:", cookie.name, cookie.value); } for (const cookie of event.deleted) { console.log("deleted:", cookie.name); } });

That listener fires for cookies your page sets, cookies a fetch() response set via Set-Cookie, and cookies removed by expiry — no polling, no pub/sub you wrote yourself. A consent banner that gets dismissed in one tab can now update every other open tab of the same origin the moment it happens.

Runs right in your browser — poke at it and watch the concept react live.

Here's the thing that actually catches people moving existing code over, and it's not a bug — it's a deliberate spec choice that reads like a footnote until it breaks something.

When you write a cookie the old way, through a Set-Cookie header or document.cookie, and you don't specify SameSite, browsers default it to Lax. That's been true for years — it's why a cookie set on your site still rides along when a user clicks a plain link to your site from somewhere else, but doesn't get sent on a cross-site POST.

cookieStore.set() doesn't inherit that default. Per the spec, if you don't pass sameSite explicitly, it defaults to "strict" — stricter than what document.cookie gives you for free. A Strict cookie is withheld on any cross-site navigation, top-level link clicks included.

So the failure mode looks like this: you migrate a cookie-setting line from document.cookie = "..." to cookieStore.set({...}), run your test suite, ship it. Everything that happens inside your own site keeps working, because same-site requests don't care about SameSite at all. Weeks later, someone clicks a link to your site from an email or a partner site, lands on a page that expects that cookie to already be there, and it isn't. No error. No console warning. The cookie you set is simply not attached to that request, because Strict said not to.

The fix is one keyword, once you know to look for it:

JavaScript
1
2
3
4
5
await cookieStore.set({ name: "session_id", value: token, sameSite: "lax", // match what document.cookie would have given you });

  • It's Chromium-only right now. Chrome and Edge support cookieStore; Firefox and Safari don't ship it as of this writing. Check the current numbers on caniuse before you rely on it for anything that isn't wrapped in a feature check — if ("cookieStore" in window) — with a document.cookie fallback.
  • It requires a secure context. Like most newer, more capable browser APIs, cookieStore simply isn't there on plain http:// origins outside localhost. If it's undefined in production but present when you test locally, that's almost certainly why.

document.cookie was never designed to be parsed — it's a string interface bolted onto a feature that predates JSON.parse existing. cookieStore treats cookies as the structured, awaitable, observable data they actually are, and the change event alone is worth the migration for anything that needs to react to a cookie set outside your own code. Just don't let sameSite default silently to something stricter than the behavior you were relying on.

Does your codebase still have a hand-rolled cookie parser in it? How old is it, and does anyone remember writing it?

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.


🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.

Thanks for reading! Let's stay connected:

Keep reading

One post a day, in your inbox

Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.

0 comments

Sign in to join the discussion, like comments, and save articles for later.