Detecting whether third-party cookies are available isn’t as simple as testing navigator.cookieEnabled or inspecting document.cookie. These APIs only report on the current first-party context and say nothing about what will happen when your code runs inside a cross-origin iframe. In practice, browsers return inconsistent results for these checks in embedded contexts — some report true inside third-party iframes even when cookies are blocked, while others behave differently. There is no shortcut for reliable detection.

// DOES NOT detect third-party cookie blocking
function areCookiesEnabled() {
  if (navigator.cookieEnabled === false) {
    return false;
  }

  try {
    document.cookie = "test_cookie=1; SameSite=None; Secure";
    const hasCookie = document.cookie.includes("test_cookie=1");
    document.cookie = "test_cookie=; Max-Age=0; SameSite=None; Secure";

    return hasCookie;
  } catch (e) {
    return false;
  }
}

This matters because the web still depends on third-party cookies for legitimate features such as single sign-on, fraud prevention, and embedded services. While browsers phase out or isolate these cookies for privacy and security reasons — a transition championed by the W3C Technical Architecture Group — the migration away from cookie-based integrations can take months or years. In the meantime, users browse with third-party cookies blocked, and embedded features silently break.

Detecting third-party cookie blocking isn’t just good technical hygiene but a frontline defense for user experience.

Consider a travel booking site that loads a partner’s iframe to display schedules. The embedded service relies on a cookie on its own domain to authenticate the user and personalize content. When the browser blocks third-party cookies, that iframe can’t access its data. Users see errors or blank screens, not a cookie policy — they see a broken booking flow. Long-term, the solution is to redesign integrations with privacy-first alternatives. Short-term, detection is essential to let applications respond gracefully.

How Browsers Actually Handle Third-Party Cookies

Detection logic must account for the fact that browsers take fundamentally different approaches to third-party cookie handling.

Safari and Firefox Block or Isolate

Safari has blocked all third-party cookies by default since version 13.1 as part of Intelligent Tracking Prevention (ITP). There are no exceptions for prior user interaction with the embedded domain. For embedded content requiring cookie access, Safari exposes the Storage Access API, which requires a user gesture to grant permission. A detection test in an iframe will nearly always fail in Safari unless that API explicitly grants access.

Firefox takes a different route. Total Cookie Protection, enabled by default since Firefox 102 in the Standard mode of Enhanced Tracking Protection, doesn’t block third-party cookies outright. Instead, it partitions them by top-level site. A cookie set by the same third-party on two different sites is stored separately and can’t be shared. A test that shows a third-party cookie was successfully set may still produce a cookie that’s useless for cross-site sessions due to this isolation. The Strict mode, by contrast, blocks third-party cookies entirely, similar to Safari.

Chrome and Edge: The Shifting Landscape

Chromium-based browsers still allow third-party cookies by default. Since Chrome 80, those cookies must carry SameSite=None; Secure or they’re rejected. Google’s plan to phase out third-party cookies entirely has shifted multiple times — originally slated for 2022, pushed to mid-2023, then postponed repeatedly through 2024. In July 2024, Google confirmed it would not unilaterally deprecate third-party cookies or force users into a new model. Instead, Chrome is moving toward a user-choice interface that lets individuals decide whether to block cookies globally, influenced by advertising industry pushback and regulatory scrutiny from the UK Competition and Markets Authority. As of 2025, third-party cookies remain enabled by default in Chrome.

Edge, also Chromium-based, shares Chrome’s SameSite handling but adds Tracking Prevention modes. The default Balanced mode blocks known trackers from Microsoft’s list while allowing third-party cookies that aren’t classified as trackers. Strict mode blocks more resource loads and can break site behavior.

Other Browsers

Privacy-focused browsers like Brave block third-party cookies by default. Internet Explorer is effectively negligible in usage now, though its default privacy settings could block third-party cookies without a valid P3P policy. Older Safari versions had partial restrictions before full ITP blocking took over.

So as of 2025, all major browsers either block or isolate third-party cookies by default — except Chrome, which still allows them in standard browsing mode while its user-choice model rolls out.

Testing in a Real Third-Party Context

Because browser behavior varies so widely, detection strategies must be grounded in context reproduction rather than browser sniffing. The reliable approach is to load your script inside an iframe on a cross-origin domain and attempt a real cookie set. That reproduces the actual conditions your integration will face. Browser detection based on names or versions is fragile — user agent strings can be spoofed, and a user’s settings can change behavior even within the same browser version.

What you’re testing for isn’t merely whether cookies work, but whether they work in the embedded, cross-origin context your integration depends on. The browser decides this based on its default policy, user settings, and any additional requirements like user gestures or Storage Access API grants. Only a real third-party test captures that full decision.

What Doesn’t Work (And Why)

Several detection techniques have been tried over the years. Most are either misleading or outright obsolete in 2025.

Basic JavaScript API Checks

Checking navigator.cookieEnabled or setting document.cookie on the main page tells you nothing about cross-site cookie status. In third-party iframes, navigator.cookieEnabled frequently returns true even when cookies are blocked. These checks are strictly first-party. Avoid them for third-party detection.

Storage Hacks

An older technique inferred cookie support by testing window.localStorage inside a third-party iframe, which was useful against older Safari versions that blocked all third-party storage. Modern browsers typically allow localStorage even when cookies are blocked, so this produces false positives and should not be used.

Server-Assisted Probes

A classic method sets a cookie from a third-party server via HTTP and then checks whether it returns on a subsequent request. This works but requires custom server-side logic, careful handling of caching, response headers, and cookie attributes like SameSite=None; Secure, plus added infrastructure complexity. It is technically valid but unsuitable for a front-end-only solution.

The Storage Access API as a Supplemental Signal

The document.hasStorageAccess() method lets embedded third-party content check for access to unpartitioned cookies. Browser support varies:

  • Chrome: Supports hasStorageAccess() and requestStorageAccess() from version 119. hasUnpartitionedCookieAccess() serves as an alias starting in version 125.
  • Firefox: Supports both methods.
  • Safari: Supports the Storage Access API, but access must be triggered by user interaction; calls to requestStorageAccess() without a gesture are ignored.

The API helps detect scenarios where cookies are present but partitioned, such as Firefox’s Total Cookie Protection. Still, treat it as a supplemental signal rather than a standalone check, because Chrome and Firefox may grant access automatically based on heuristics or site engagement.

Iframe + postMessage: The Current Best Practice

Despite the Storage Access API’s availability, the most reliable, browser-compatible approach remains:

  1. Embed a hidden iframe from a third-party domain.
  2. Inside the iframe, attempt to set a test cookie.
  3. Report success or failure to the parent via window.postMessage.

This simulates a real third-party scenario and works across all major browsers when properly configured. It doesn’t require server logic, but you do need a cross-site domain and a static file.

For reference, Chrome 133 introduced Sec-Fetch-Storage-Access, an HTTP header sent with cross-site requests to indicate unpartitioned cookie access. It’s server-only and not accessible from JavaScript, so it’s relevant for back-end analytics but not client-side detection. As of May 2025, only Chrome supports it.

Implementation Steps

Host a minimal page on a third-party domain, e.g., https://cookietest.example.com/cookie-check.html:

<!DOCTYPE html>
<html>
  <body>
    <script>
      document.cookie = "thirdparty_test=1; SameSite=None; Secure; Path=/;";
      const cookieFound = document.cookie.includes("thirdparty_test=1");
    
      const sendResult = (status) => window.parent?.postMessage(status, "*");
    
      if (cookieFound && document.hasStorageAccess instanceof Function) {
        document.hasStorageAccess().then((hasAccess) => {
          sendResult(hasAccess ? "TP_COOKIE_SUPPORTED" : "TP_COOKIE_BLOCKED");
        }).catch(() => sendResult("TP_COOKIE_BLOCKED"));
      } else {
        sendResult(cookieFound ? "TP_COOKIE_SUPPORTED" : "TP_COOKIE_BLOCKED");
      }
    </script>
  </body>
</html>

Serve it over HTTPS, and set the cookie with SameSite=None; Secure. Without these attributes, modern browsers reject the cookie silently.

Step 2: Embed the Iframe and Listen

On the main page, embed the iframe and handle the result message:

function checkThirdPartyCookies() {
  return new Promise((resolve) => {
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = "https://cookietest.example.com/cookie-check.html"; // your subdomain
    document.body.appendChild(iframe);

    let resolved = false;
    const cleanup = (result, timedOut = false) => {
      if (resolved) return;
      resolved = true;
      window.removeEventListener('message', onMessage);
      iframe.remove();
      resolve({ thirdPartyCookiesEnabled: result, timedOut });
    };

    const onMessage = (event) => {
      if (["TP_COOKIE_SUPPORTED", "TP_COOKIE_BLOCKED"].includes(event.data)) {
        cleanup(event.data === "TP_COOKIE_SUPPORTED", false);
      }
    };

    window.addEventListener('message', onMessage);
    setTimeout(() => cleanup(false, true), 1000);
  });
}

Example usage:

checkThirdPartyCookies().then(({ thirdPartyCookiesEnabled, timedOut }) => {
  if (!thirdPartyCookiesEnabled) {
    someCookiesBlockedCallback(); // Third-party cookies are blocked.
    if (timedOut) {
      // No response received (iframe possibly blocked).
      // Optional fallback UX goes here.
      someCookiesBlockedTimeoutCallback();
    };
  }
});

Step 3: Add Storage Access API Fallback

In Safari, users can grant access via the Storage Access API but only in response to a user gesture. In your test page iframe:

<button id="enable-cookies">This embedded content requires cookie access. Click below to continue.</button>

<script>
  document.getElementById('enable-cookies')?.addEventListener('click', async () => {
    if (document.requestStorageAccess && typeof document.requestStorageAccess === 'function') {
      try {
        const granted = await document.requestStorageAccess();
        if (granted !== false) {
          window.parent.postMessage("TP_STORAGE_ACCESS_GRANTED", "*");
        } else {
          window.parent.postMessage("TP_STORAGE_ACCESS_DENIED", "*");
        }
      } catch (e) {
        window.parent.postMessage("TP_STORAGE_ACCESS_FAILED", "*");
      }
    }
  });
</script>

On the parent page, listen for this message and retry detection:

// Inside the same `onMessage` listener from before:
if (event.data === "TP_STORAGE_ACCESS_GRANTED") {
  // Optionally: retry the cookie test, or reload iframe logic
  checkThirdPartyCookies().then(handleResultAgain);
}

Client-Side Fallback (When You Have No Second Domain)

If you can’t host content on a separate domain, the iframe method isn’t possible. Your only option is to combine signals — basic cookie checks, hasStorageAccess(), localStorage behavior, and passive indicators like load failures — to infer whether third-party cookies are blocked. This method is not fully accurate, but it’s better than nothing in constrained environments.

Basic implementation:

async function inferCookieSupportFallback() {
  let hasCookieAPI = navigator.cookieEnabled;
  let canSetCookie = false;
  let hasStorageAccess = false;

  try {
    document.cookie = "testfallback=1; SameSite=None; Secure; Path=/;";
    canSetCookie = document.cookie.includes("test_fallback=1");

    document.cookie = "test_fallback=; Max-Age=0; Path=/;";
  } catch (_) {
    canSetCookie = false;
  }

  if (typeof document.hasStorageAccess === "function") {
    try {
      hasStorageAccess = await document.hasStorageAccess();
    } catch (_) {}
  }

  return {
    inferredThirdPartyCookies: hasCookieAPI && canSetCookie && hasStorageAccess,
    raw: { hasCookieAPI, canSetCookie, hasStorageAccess }
  };
}

Example usage:

inferCookieSupportFallback().then(({ inferredThirdPartyCookies }) => {
  if (inferredThirdPartyCookies) {
    console.log("Cookies likely supported. Likely, yes.");
  } else {
    console.warn("Cookies may be blocked or partitioned.");
    // You could inform the user or adjust behavior accordingly
  }
});

This fallback is reasonable when:

  • You ship a JavaScript-only widget embedded on unknown sites.
  • You don’t control a second domain.
  • You need visibility into user-side behavior for debugging.

Don’t base security-critical logic (like auth gating) on it, but use it to tailor UX or decide whether to attempt a fallback flow.

What to Do When Cookies Are Blocked

Detection is only half the problem. These strategies help you adapt when third-party cookies are unavailable.

Redirect-Based Flows

For authentication, move away from embedded iframes. Redirect the user to the identity provider’s site for login, then redirect back. This works in all browsers at the cost of a less seamless experience.

Request Storage Access

Prompt users with requestStorageAccess() after a clear UI gesture; this is mandatory in Safari. It re-enables cookies without leaving the page.

Token-Based Messaging

Pass session info from parent to iframe using postMessage (with a valid origin parameter) or query parameters with signed JWTs. This eliminates cookie dependency entirely but requires coordination between both sides:

// Parent
const iframe = document.getElementById('my-iframe');

iframe.onload = () => {
  const token = getAccessTokenSomehow(); // JWT or anything else
  iframe.contentWindow.postMessage(
    { type: 'AUTH_TOKEN', token },
    'https://iframe.example.com' // Set the correct origin!
  );
};

// iframe
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://parent.example.com') return;

  const { type, token } = event.data;

  if (type === 'AUTH_TOKEN') {
    validateAndUseToken(token); // process JWT, init session, etc
  }
});

Partitioned Cookies (CHIPS)

Since Chrome 114, Chromium browsers support the Partitioned cookie attribute, which isolates cookies per top-level site. Useful for widgets like chat or embedded forms that don’t need cross-site identity.

Note: Firefox and Safari don’t support the Partitioned attribute. Firefox partitions cookies via Total Cookie Protection; Safari blocks third-party cookies outright.

Be aware that these cookie may appear “blocked” in basic detection checks, so adjust your logic if needed.

Looking Forward

Third-party cookies are fading, but the transition is gradual and uneven. Your job is to bridge the gap between technical limits and real-world experience.

  • Watch the standards: FedCM, Topics, Attribution Reporting, and Fenced Frames are reshaping identity and analytics without cross-site cookies.
  • Combine detection with graceful fallback: Redirect flows, requestStorageAccess(), and token-based messaging each improve UX incrementally.
  • Inform users: A clear, friendly message can prevent confusion when something works in one browser but not another.

You don’t need a perfect solution, just a resilient one. Early detection with thoughtful handling protects users and your architecture. Remember, Chrome’s path has shifted based on feedback and evolving realities — the transition is not always linear. And having something is better than nothing.

Smashing Editorial