Skip to content

Storage · Concept

Partitioned cookies (CHIPS)

Published Updated

Cookies Having Independent Partitioned State (CHIPS) lets a server opt one cookie into partitioned storage with the Partitioned attribute on Set-Cookie, so an embedded third party gets a separate cookie jar on every top-level site that embeds it. The cookie still works for an embedded chat widget or map on a given site, and stops working as a cross-site identifier, because the jar set under retail.example is not readable under news.example.

A partitioned cookie is double-keyed. The browser stores it under the pair of the host that set it and a partition key, which is the site (scheme plus registrable domain) of the top-level page the browser was showing when the request that set the cookie started. A cookie set by support.chat.example while embedded on https://retail.example is keyed {("support.chat.example"), ("https", "retail.example")}; the same embed on another site gets an empty jar and sets its own cookie there (Cookies Having Independent Partitioned State, privacysandbox.google.com).

The attribute is accepted only together with Secure, so it is HTTPS-only by construction, and the __Host- prefix is recommended so the cookie is bound to one hostname rather than to the registrable domain. SameSite=None is what allows the cookie to be sent in a third-party context at all; without it the cookie would be omitted from embedded requests regardless of partitioning.

Set-Cookie: __Host-session=5f2b; Secure; Path=/; SameSite=None; Partitioned;

A cookie that omits Partitioned is unpartitioned and falls under whatever third-party-cookie policy the browser applies: blocked by default in Firefox’s strict mode and in Safari, and subject to Chrome’s user settings.

Chrome stores at most 180 cookies per partition and caps the partition at 10 KB per embedded site; a widget that sets many small cookies on a busy top-level site hits the count limit first. Each top-level site is a separate partition, so the same widget embedded on a thousand sites holds a thousand jars, each independently subject to the limits.

Relationship to Firefox State Partitioning

Section titled “Relationship to Firefox State Partitioning”

Firefox partitions every third-party cookie and storage API by top-level site in Enhanced Tracking Protection’s strict mode and in private windows, with no attribute required, and partitions network state (HTTP cache, DNS, connection pools) permanently (State Partitioning, developer.mozilla.org). CHIPS is the opt-in that behaves the same in each engine, which is why the attribute is the portable choice even where default partitioning exists.

The attribute shipped in Chrome 114 after an origin trial from Chrome 100 to 116, in Firefox 141, and in Safari 26.2 (BCD http.headers.Set-Cookie.Partitioned). An engine that does not recognise Partitioned ignores the attribute and stores the cookie unpartitioned, which is why the Secure and SameSite=None requirements matter: they are what keeps the fallback cookie usable.

One example sets the cookie from the embedded server; the other inspects partition state from script where the browser allows it.

Section titled “Setting a partitioned session cookie from an embedded service”

The server of the embedded origin sets the cookie on the response to the first request from the iframe. Nothing in the embedding page changes.

// Express handler on support.chat.example
app.get('/widget/session', (req, res) => {
res.setHeader(
'Set-Cookie',
'__Host-chat=9b1f; Secure; Path=/; SameSite=None; Partitioned; Max-Age=86400'
);
res.json({ ok: true });
});

On the next request from the same iframe on the same top-level site the cookie is present; on a different top-level site the server sees no cookie and starts a fresh session, which is the intended behaviour.

Section titled “Reading partition state from the Cookie Store API”

document.cookie returns name=value pairs only, so script cannot tell a partitioned cookie from an unpartitioned one through it. The Cookie Store API (Chrome 87, Firefox 140, Safari 18.4) exposes a partitioned boolean on each cookie.

async function partitionedCookies() {
if (!('cookieStore' in window)) {
return null; // no Cookie Store API: partition state is not observable from script
}
const cookies = await window.cookieStore.getAll();
return cookies.filter((cookie) => cookie.partitioned);
}

A null result is the signal to rely on the server’s view instead: the server can log whether the __Host- cookie arrived and under which Sec-Fetch-Site value.

Specifications

SpecificationStatus
None.