# Partitioned cookies (CHIPS)

> How the Set-Cookie Partitioned attribute gives an embedded site a cookie jar per top-level site, the Secure and __Host- rules, and engine support.

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`.

## How it works

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](https://privacysandbox.google.com/cookies/chips),
privacysandbox.google.com).

### Attribute requirements

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.

```http
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.

### Limits and partitions

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

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](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/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.

### Support

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.

## Examples

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

### 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.

```js
// 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.

### 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.

```js
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.

:::observed
Chrome DevTools, Application > Storage > Cookies, shows a **Partition Key** column; for a cookie
set with `Partitioned` it contains the top-level site (for example `https://retail.example`), and
for every other cookie it is empty ([View, edit, and delete cookies](https://developer.chrome.com/docs/devtools/application/cookies),
developer.chrome.com). Setting the same cookie without `Secure` leaves the column empty and the
cookie unpartitioned, with no warning in the Console; the Issues panel is where Chrome reports a
rejected `Set-Cookie`.
:::

## See also

- [Cookies Having Independent Partitioned State (CHIPS)](https://privacysandbox.google.com/cookies/chips) (privacysandbox.google.com)
- [Cookies Having Independent Partitioned State specification](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html) (ietf.org)
- [Partitioned cookies](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Third-party_cookies/Partitioned_cookies) (developer.mozilla.org)
- [Storage Access API](/reference/storage/storage-access-api/)
- [localStorage and sessionStorage](/reference/storage/localstorage/)
- [Eviction and best-effort storage](/reference/storage/eviction/)