JavaScript Storage Access Table
| Piece | What it does | Field note |
|---|---|---|
the partition | The default world | cross-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 check | promise: true = unpartitioned access already flows; false = the jar you see is not your jar - never trust auth reads before it |
document.requestStorageAccess() | The ask | resolves on grant (auto or prompted), rejects on deny - rejection is a designed outcome, not an error to silence |
engagement heuristics | The grader | auto-grant needs recent first-party interaction with your top-level site; prompt without history = most browsers deny outright |
user activation rule | The gesture gate | requests on prompt-happy browsers must run inside a click handler in the frame - iframe-boot calls throw or auto-reject |
grant expiry | The clock | grants expire under browser-specific inactivity windows (Safari strictest) and void on profile switches - re-verify per session |
requestStorageAccessFor | The sibling | top-level site requests access for an embedded same-site resource - built for related-website-sets, no per-frame prompt churn |
the migration rule | The era | third-party cookies are leaving: partitioned by default, storage access for embedded identity, requestStorageAccessFor for site-family identity |
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
- 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.
- 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.
- 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.
- 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.
- 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.