Partitioned Cookies: CHIPS for Third-Party Embeds

Add Partitioned to a cross-site cookie and the browser files it under two keys instead of one: the host that set it, and the site of the top-level page it was set on. CHIPS is the name of that behaviour. It is how an embedded widget keeps a session alive in a browser that otherwise blocks third-party cookies.

The attribute goes on the end of a Set-Cookie header you already send:

Set-Cookie: __Host-example=34d8g; SameSite=None; Secure; Path=/; Partitioned;

That is MDN’s example.

What does the Partitioned attribute change?

Without it, one cookie jar per host. Your widget at widget.example sets a session cookie, and that same cookie comes back whether the user is on shop-a.example or shop-b.example. That single shared jar is what makes cross-site tracking possible, and it is why browsers started taking it away.

With Partitioned, the cookie is double-keyed. Chrome’s documentation gives the storage key as a pair, the host that set the cookie plus the site of the top-level URL the browser was visiting at the time:

{("https://site-a.example"), ("3rd-party.example")}

So the cookie your widget sets inside a frame on site-a.example exists only on site-a.example. On site-b.example the widget starts with an empty jar and sets a second, separate cookie. Google’s CHIPS documentation puts the boundary plainly: a partitioned cookie is “tied to the top-level site where it’s initially set and cannot be accessed from elsewhere”. That holds even when the user later visits your service directly, as a top-level site of its own.

Partitioning is by site, not origin, in every engine that has shipped it. Safari made that explicit when it shipped: “Safari partitions cookies by site (not origin), matching other browsers.” Subdomains of the same registrable domain share a partition.

Why did the embed lose its session in the first place?

Because two of the three engines already decided this for you.

Firefox turns on Total Cookie Protection whenever Enhanced Tracking Protection is on, which is the default, and that, in MDN’s words, “gives third-party cookies a separate cookie jar per site”. Safari applies its tracking prevention policy by default through ITP. Chrome is the outlier: it does not block third-party cookies by default, only in Incognito, or when a user goes into chrome://settings and blocks them.

Chrome is also not going to change that. In April 2025 the Privacy Sandbox team wrote that they had “made the decision to maintain our current approach to offering users third-party cookie choice in Chrome, and will not be rolling out a new standalone prompt for third-party cookies”. Read the announcement carefully though. It means the deadline went away, not the problem. Your embed still breaks for every Safari user, every Firefox user on default settings, and every Chrome user in Incognito or with the setting flipped.

Set the header. There is nothing clever to it, and no library is required:

import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';

createServer((req, res) => {
  res.setHeader('Set-Cookie', [
    '__Host-widget-session=' + randomUUID() +
      '; Max-Age=1800' +
      '; Path=/' +
      '; Secure' +
      '; HttpOnly' +
      '; SameSite=None' +
      '; Partitioned',
  ]);
  res.setHeader('Content-Type', 'application/json');
  res.end('{"ok":true}');
}).listen(3000);

Three attributes are not optional. Secure is required by the specification, so the cookie has to come from HTTPS. SameSite=None is what allows the cookie to be sent in a cross-site context at all, and Safari blocks the cookie outright if either is missing. Path=/ and the absence of a Domain attribute are what the __Host- prefix demands, and MDN recommends that prefix for partitioned cookies specifically, to bind them “to the hostname and not the registrable domain”.

The client-side form works the same way:

document.cookie = 'TestCookie=12345; SameSite=None; Secure; Partitioned';

A browser that has never heard of the attribute will not choke on it. RFC 6265 says user agents “ignore unrecognized cookie attributes (but not the entire cookie)”, so an old browser stores your cookie exactly as it did yesterday. Adding Partitioned is additive.

Which browsers support partitioned cookies?

Chrome has had it on by default since Chrome 114, and Edge since the same version. Firefox shipped it in 141.

Safari is the awkward one. Support arrived in Safari 18.4, went away again in 18.5, and came back in Safari 26.2 on 12 December 2025, which WebKit announced in exactly those terms: “Safari 26.2 ships support for CHIPS (Cookies Having Independent Partitioned State) once again.” That December date is why MDN now lists partitioned cookies as Baseline Newly available since December 2025, and it is the reason to check your own analytics before assuming the feature is universally there. Anyone on Safari 18.5 through 26.1 is running an engine that understands Secure and SameSite=None and quietly ignores Partitioned, which MDN’s compatibility table records as an added-then-removed entry.

What will partitioned cookies not do?

They will not give you shared state across top-level sites. That is deliberate. The CHIPS explainer lists cross-site single sign-on among its non-goals, so if your product signs a user in once and expects that session to be there on every customer’s domain, Partitioned does not rescue the design. It writes the loss into the cookie.

For that case the tool is the Storage Access API, which prompts the user for permission to use unpartitioned cookies on a per-frame basis. It costs a user gesture and a permission dialogue on each site, which is worse for conversion than a silent cookie and is the only route left once the jar is split.

There are limits on the jar, too. Chrome allows “maximum 180 cookies per partition that cannot exceed 10 KB per-embedded-site”. Multiply that by every top-level site your widget appears on and it is generous, but a design that stuffs state into cookies will find the ceiling.

CHIPS also covers cookies and nothing else. The explainer puts storage partitioning for localStorage, IndexedDB and service workers outside its scope, because browsers handle those separately, so moving session state out of a cookie and into localStorage does not hand you back a shared jar.

How do you test it?

Chrome ships a flag that simulates a world with no unpartitioned third-party cookies. Turn on chrome://flags/#test-third-party-cookie-phaseout, then load a page that embeds your widget and open Application > Storage > Cookies in DevTools. Partitioned cookies show the partition key of the top-level site next to them. A cookie that failed to set gets highlighted in yellow with the tooltip “This cookie was blocked due to user preferences”, which is the fastest way to catch a missing Secure or a stray Domain attribute.

Then check the header itself, because most of the failures are typos in a string you built by concatenation:

curl -sI https://widget.example.com/api/session | grep -i '^set-cookie:'

And test in Safari as well as Chrome, on a real recent version. Safari’s rule that the cookie is blocked when SameSite=None or Secure is missing is stricter than what you will observe in Chrome, so Chrome-only testing will pass code that fails the moment it meets an iPhone.

If you build embedded software, set Partitioned now and treat per-site state as the only state you get. The header edit takes a minute. The longer job is auditing the code that assumed one session per user, when what you have from here is one session per user per embedding site.

If the product genuinely needs identity across customer sites, stop asking cookies to carry it. Shopify’s embedded apps gave up on that years ago and authenticate with a signed session token on every request, which we walk through in verifying Shopify session tokens in Node. And because SameSite=None is mandatory here, the cookie does travel on cross-site requests, so CSRF protection has to come from somewhere else, such as the Sec-Fetch-Site header checks we covered previously.

Whoooop builds and maintains embedded commerce software, including Shopify apps that live inside an iframe in the admin and storefront widgets that have to keep working in Safari. If an integration of yours has started losing sessions in one browser and not another, our Shopify development work is usually where that conversation starts.

Need this built properly?

Whoooop Ltd has spent 15+ years building and maintaining web applications in TypeScript, React, Node.js and serverless — the same ground this post covers.

Get in touch