JavaScript Storage Access Table

PieceWhat it doesField note
the partitionThe default worldcross-site iframes get a (top-site, your-site) keyed jar - your widget works but carries no memory of the user who signed in on your domain
document.hasStorageAccess()The checkpromise: true = unpartitioned access already flows; false = the jar you see is not your jar - never trust auth reads before it
document.requestStorageAccess()The askresolves on grant (auto or prompted), rejects on deny - rejection is a designed outcome, not an error to silence
engagement heuristicsThe graderauto-grant needs recent first-party interaction with your top-level site; prompt without history = most browsers deny outright
user activation ruleThe gesture gaterequests on prompt-happy browsers must run inside a click handler in the frame - iframe-boot calls throw or auto-reject
grant expiryThe clockgrants expire under browser-specific inactivity windows (Safari strictest) and void on profile switches - re-verify per session
requestStorageAccessForThe siblingtop-level site requests access for an embedded same-site resource - built for related-website-sets, no per-frame prompt churn
the migration ruleThe erathird-party cookies are leaving: partitioned by default, storage access for embedded identity, requestStorageAccessFor for site-family identity
Reference: the MDN Storage Access API. The sanctioned unlock after the third-party cookie broke: embedded frames ask for their own unpartitioned storage, the browser grades the ask against user engagement, and identity continuity becomes a privilege - granted, never assumed.
Bottom line: ask-check-act, with rejection as the normal case. Check hasStorageAccess before believing any auth state, fire requestStorageAccess from a real click inside the frame, re-verify grants per session (they expire), and keep everything that works partitioned partitioned - every prompt you avoid is policy risk you shed. The silent empty-jar sign-out is the integration bug of the cookie-deprecation era; your telemetry should track grant versus reject as a product metric.
Related tools: the localStorage table (the partitioned shelf), the IndexedDB table (partitioned too), the broadcast channel table (the partitioned bus), the postMessage table (iframe communication that still flows), and the PWA install table (the engagement signal behind auto-grants).

The Storage Access API exists because browsers broke the third-party cookie: an embedded document (your widget inside someone else's page - a chat box, a payment frame, a comment section) used to share cookies and storage with its own top-level site, and modern browsers now partition or block that storage in cross-site iframes by default. The API is the sanctioned unlock: the embedded frame asks the browser for access to its own unpartitioned cookies, the browser evaluates the request against user-activity heuristics, and the user may see a prompt - identity continuity for the embedded case, without reopening the whole third-party cookie jar.

Bottom line: the flow is ask-check-act. hasStorageAccess() is a promise answering whether this frame already has unpartitioned access (same-site frames and previously granted frames return true instantly); requestStorageAccess() initiates the grant, resolving when access exists (already granted, auto-granted by heuristics like recent top-level interaction, or user-approved) and rejecting when the browser denies (no prior engagement, blocked by policy, or the user said no). Design around rejection as the normal case: storage access is a privilege the browser grants to embedded content the user actually uses, not a default.

The era context is why this API keeps growing: as Chrome joins Safari and Firefox in restricting third-party cookies, every embedded cross-site identity flow - SSO in a frame, cart continuity in an embedded shop, a comment widget that knows you - routes through this API or its newer sibling requestStorageAccessFor (which lets the top-level site request access on behalf of an embedded same-site resource, no per-frame prompt churn). The migration is not optional: in a partitioned world, unpartitioned storage is granted, never assumed.

How to use

  1. Check before asking: const hasAccess = await document.hasStorageAccess(); - true means unpartitioned cookies and storage already flow; skip straight to your identity read. false means partitioned (or blocked): the jar you see is not your jar.
  2. Ask for the grant: try { await document.requestStorageAccess(); } catch (e) { showFallback(); } - resolves if granted (possibly with a user prompt), rejects otherwise; the rejection is a designed outcome, not an error to silence.
  3. Gate the prompt with engagement: the browser auto-grants when the embedded site has recent first-party interaction (a visit to your top-level site, stored settings); prompt without that history and most browsers deny outright - route users through your top-level site first when the flow allows it.
  4. Handle the user-gesture rule: requestStorageAccess() must run in a user-activation context (a click handler on something inside the frame) on prompt-happy browsers - calling it on load throws or auto-rejects; wire it to the sign-in button, not the iframe boot sequence.
  5. Read what you were granted: after resolution, document.cookie reflects the unpartitioned jar and fetch sends your cookies (credentials: 'include') - verify with a round-trip before claiming the user is signed in; and remember the grant is scoped to this frame-tree and expires under browser-specific inactivity windows.

Frequently asked questions

What exactly does partitioned storage mean for my embedded widget?

The browser gives your frame a separate cookie jar and storage partition keyed by both the top-level site and yours - (top-site, your-site) - so your widget still works (its localStorage, IndexedDB and cookies function) but carries no memory of the user who just signed in on your top-level site. Concretely: a chat widget works fine partitioned for a guest session; the same widget cannot recognize the returning user, because the session cookie it set on site A is invisible when embedded on site B. That identity continuity - one login everywhere your embed ships - is precisely what the Storage Access API restores on request. Two design consequences before you even touch the API. Audit what your widget actually needs: many features work partitioned and should stay partitioned (less prompt surface, less policy risk). And test the distinction explicitly in the browser flags that force partitioning - discovering in production that your auth reads returned an empty jar is the classic integration failure of the cookie-deprecation era.

When does requestStorageAccess() actually show a prompt, and what decides the answer?

The browser decides by engagement heuristics, and the prompt is the last resort, not the default. The auto-grant path: the user recently interacted with your site as a top-level site (visited and engaged within the browser-defined window - roughly 30 days on Safari with site interaction, shorter elsewhere), or the frame already holds a grant - in these cases the promise resolves with no UI. The prompt path: some engagement history exists but not enough, and the browser asks the user allow-or-deny - this requires user activation inside the frame on Safari and Chrome, so a request fired during iframe load has nothing to attach the activation to and fails. The deny path: no meaningful history with your site, blocked-by-policy categories, or a prior explicit denial (which sticks until the user resets it in site settings). The design translation: your embedded sign-in button is the right request point (real user activation, clear context for the prompt), your README should tell integrators that first-time embeds may prompt once, and your telemetry should bucket resolve versus reject - rejection rates are the metric that tells you whether your engagement loop (getting users to visit your top-level site) is working.

How is this different from just using partitioned storage, or from the old Storage Access shim patterns?

They solve different problems, and picking by problem keeps the permission surface small. Partitioned storage needs no permission and survives everywhere - it is the right home for per-embed state that never needs to recognize the user across sites (draft comments, guest carts, UI preferences). Storage Access is for the narrow and valuable case of identity continuity: the user signed in on your domain, and the embed needs that fact. The old shim patterns - storage events as a bus, window.name smuggling, BroadcastChannel across tabs - are blocked or partitioned now (window.name is reset cross-site; BroadcastChannel is partitioned), so they are not alternatives, they are archaeology. The newer sibling matters in the same comparison: requestStorageAccessFor (Chrome) lets the top-level site request cookie access for an embedded same-site resource without embedding it in a frame tree that prompts per frame - built for the related-website-set / first-party-set world where a family of domains shares identity. Choose partitioned by default, requestStorageAccess for embedded identity, requestStorageAccessFor for site-family identity, and document which of the three each feature uses.

What breaks in practice, and what is the migration checklist?

The recurring failures, in order of frequency. Silent empty-auth: the widget reads document.cookie, gets the partitioned jar (empty), and renders signed-out - fix by checking hasStorageAccess() before believing any auth state, and surfacing a real sign-in action when false. Load-time requests: requestStorageAccess() during iframe boot with no user activation - move the request into the click handler. Assuming permanence: grants expire under inactivity windows that differ per browser (Safari is the strictest), and incognito/profile switches void them - re-verify per session, never cache the boolean in your own storage across sessions. Popup-blocker collisions: some integrations open the top-level sign-in in a new tab from inside the frame - popups without activation are blocked, so the fallback link must be a real anchor. And cross-browser drift: Safari pioneered the API with strict heuristics, Chrome matches the surface plus requestStorageAccessFor plus related website sets, Firefox ships the API with its own engagement rules - test all three, and treat browser differences as product requirements, not bugs. The checklist: audit cookie reliance in embeds, make everything partitionable partitioned, wire requests to user gestures, verify grants per session, and monitor reject rates as a product metric.

Related tools