Why Firebase says auth/popup-blocked, and how to get the sign-in popup to open
auth/popup-blocked means the browser refused to open Firebase's sign-in window, so window.open() returned nothing inside signInWithPopup.
Call signInWithPopup straight from the click handler with nothing slow awaited before it (including await setPersistence(...)), and fall back to signInWithRedirect where popups can't open at all.
What the error means
The message that comes with the code is "Unable to establish a connection with the popup. It may have been blocked by the browser." It is defined in the Firebase JS SDK's error list, and the code constant is AuthErrorCodes.POPUP_BLOCKED ("auth/popup-blocked").
In the browser SDK it is raised right after the call to window.open() that opens the sign-in window: if the browser hands back no window, the SDK raises auth/popup-blocked. So this is a browser decision, not a problem with your Firebase project settings. Settings problems have their own codes, such as auth/unauthorized-domain.
Why browsers block the popup
Browsers only open a new window when the user has just done something, such as a click or a key press:
- MDN: "Popup windows must be opened in direct response to user input, and a separate user gesture event is required for each
Window.open()call." window.open()returnsnull"if the browser fails to open the new browsing context, for example because it was blocked by a browser popup blocker."- That permission is called transient activation. MDN says it "expires after a timeout (if not renewed by further interaction) and may also be consumed by some APIs (like
Window.open())." - The HTML standard leaves the exact length to each browser, and says it "is expected be at most a few seconds."
The important detail: an await in your click handler does not cancel the click by itself. What matters is how much time passes before Firebase reaches window.open(). A quick await may still leave time; a slow one (a network request, a storage write, loading a module) can use up the window. That is why this error can show up for some users and not others, or on a slow connection but not on a fast one.
The SDK also does some async work of its own before it opens the window: it awaits its own popup setup (a hidden helper frame) and builds the sign-in URL. On some browsers the SDK starts that setup as soon as auth is created, so create your auth instance when the page loads, not inside the click handler, to avoid adding more delay to the click.
Common causes
- Awaiting something before
signInWithPopup. The most common versions: -await setPersistence(auth, ...)right before sign-in. Firebase's docs say a sign-in call made after changing persistence "will wait for that persistence change to complete", so you don't need theawait. The docs also say the default for web browsers is already local persistence, sosetPersistence(auth, browserLocalPersistence)usually isn't needed at all. -await fetch(...)(logging, feature flags, a token request) before sign-in. -await import('firebase/auth')or loading the Firebase config inside the click handler. - No click at all. Calling
signInWithPopupon page load, in a ReactuseEffect, insideonAuthStateChanged, or from a timer. There is no user gesture for the browser to honor. - Running inside a sandboxed iframe. Many app builders show your app inside a preview frame. MDN says that in a sandboxed
<iframe>without theallow-popupskeyword, popups "will silently fail." A FlutterFlow user reported this exact case on May 27, 2026: the console blamed a "sandboxed frame whose 'allow-popups' permission is not set," and Google sign-in worked once the app was published. - An in-app browser. Links opened inside some apps load in an embedded webview. Google blocks its OAuth sign-in page in embedded webviews (since September 30, 2021, with the error
disallowed_useragent), and notes that a "messaging or news application" opening links to your site can affect it. A redirect fallback doesn't help here, because Google refuses the webview itself. Show an "Open this page in your browser to sign in" message instead. - The user's own popup blocker setting for your site. MDN names browser popup blockers as a reason
window.open()returnsnull.
Fix: open the popup first, do other work after
Before (can be blocked, depending on how long the awaits take):
import {
getAuth, GoogleAuthProvider, signInWithPopup,
setPersistence, browserLocalPersistence,
} from 'firebase/auth';
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInButton.addEventListener('click', async () => {
// Each await spends part of the click's short activation window.
await setPersistence(auth, browserLocalPersistence); // a storage write
await fetch('/api/log-sign-in-attempt'); // a network round trip of unknown length
const result = await signInWithPopup(auth, provider); // may now be too late
console.log(result.user.uid);
});
On a fast connection this can still open the popup, which is why it passes in testing. When the awaits take longer than the browser allows, the same code throws auth/popup-blocked.
After (popup first, nothing awaited before it):
import {
getAuth, GoogleAuthProvider, signInWithPopup,
setPersistence, browserSessionPersistence,
} from 'firebase/auth';
// At startup, outside any click handler.
const auth = getAuth();
const provider = new GoogleAuthProvider();
// Only if you want something other than the default (local) persistence:
setPersistence(auth, browserSessionPersistence).catch(console.error);
signInButton.addEventListener('click', () => {
// First call in the handler.
signInWithPopup(auth, provider)
.then((result) => console.log(result.user.uid))
.catch(handleSignInError);
fetch('/api/log-sign-in-attempt'); // other work can start after the call
});
If persistence depends on a "Remember me" checkbox, set it in the handler without await. Firebase's docs say the sign-in call waits for the persistence change:
signInButton.addEventListener('click', () => {
setPersistence(
auth,
rememberMe.checked ? browserLocalPersistence : browserSessionPersistence,
).catch(console.error); // no await
signInWithPopup(auth, provider)
.then((result) => console.log(result.user.uid))
.catch(handleSignInError);
});
The same rule applies in React, Vue, Svelte or Angular: call signInWithPopup directly in the onClick (or equivalent) handler, not in an effect or after other awaited calls.
Fallback: catch auth/popup-blocked and redirect
When the popup really can't open, signInWithRedirect signs the user in with a full-page redirect instead, and getRedirectResult picks up the result when the page loads again:
import {
getAuth, GoogleAuthProvider, AuthErrorCodes,
signInWithPopup, signInWithRedirect, getRedirectResult,
} from 'firebase/auth';
const auth = getAuth();
const provider = new GoogleAuthProvider();
// On every page load: finish a redirect sign-in if one is in progress.
getRedirectResult(auth)
.then((result) => { if (result) console.log(result.user.uid); })
.catch(handleSignInError);
signInButton.addEventListener('click', () => {
signInWithPopup(auth, provider)
.then((result) => console.log(result.user.uid))
.catch((error) => {
if (error.code === AuthErrorCodes.POPUP_BLOCKED) {
return signInWithRedirect(auth, provider); // leaves the page
}
handleSignInError(error);
});
});
Before you rely on it:
- Redirect sign-in has its own setup requirement. Firebase's docs (checked Oct 2026): "Starting June 24 2024, implementing one of the options will be required for redirect sign-in to work on Google Chrome M115+. This is already required on Firefox 109+ and Safari 16.1+." Without one of those options you can swap this error for signInWithRedirect not working. Popup or redirect? compares the two.
- The page leaves your app. Save anything the user typed before redirecting.
- Only redirect on a real block.
auth/popup-closed-by-usermeans the window did open; see auth/popup-closed-by-user for that case. - Not for in-app browsers (see cause 4).
How to confirm it is this problem, and that the fix worked
- Log the code. In your
catch, logerror.code. It should be exactlyauth/popup-blocked. Other codes need other fixes; see the Firebase auth errors list. - Check the click is still active. Right before
signInWithPopup, addconsole.log(navigator.userActivation?.isActive). MDN saysisActive"indicates whether the window has transient user activation."falsemeans your code already used up the click. After the fix it should logtrue. - Rule out the preview frame. Open the published app in its own browser tab, not inside a builder's preview, and try again.
- Rule out the in-app browser. Open the same link in your normal browser.
- Confirm the fix. The popup opens on the first click, and the promise resolves with
result.user.
Related problems
- A double click starts two popups and the first fails with auth/cancelled-popup-request.
- The popup opens but sign-in reports the window closed: auth/popup-closed-by-user.
FAQ
Does auth/popup-blocked mean my Firebase config is wrong?
No. The SDK throws it when window.open() returns no window, which is the browser's decision. Config problems have their own codes, such as auth/unauthorized-domain.
Do I need to await setPersistence before signInWithPopup?
No. Firebase's docs say a sign-in call made after changing persistence waits for that change to complete. Call setPersistence at startup, or in the handler without await, then call signInWithPopup.
Why does it only fail in my app builder's preview?
Previews often run your app inside a sandboxed iframe. Without allow-popups, popups from that frame silently fail, per MDN. Test the published URL in its own tab.
Why does it work on my laptop but not on a phone?
On a slower device or connection, awaited work before signInWithPopup can take long enough to use up the click's activation window. Firebase's Google sign-in guide also says "The redirect method is preferred on mobile devices." If the link was opened inside another app, see the in-app browser cause above.
Should I just switch to signInWithRedirect?
Maybe, but it is not a drop-in swap: Firebase's docs say redirect sign-in needs one of their setup options on current Chrome, Firefox and Safari versions. A popup-first flow with a redirect fallback keeps redirect for the cases that need it.
Sources
- Firebase JS SDK source: packages/auth/src/core/errors.ts (POPUP_BLOCKED message and code)
- Firebase JS SDK source: packages/auth/src/platform_browser/util/popup.ts (window.open, then the POPUP_BLOCKED assert)
- Firebase JS SDK source: packages/auth/src/core/strategies/abstract_popup_redirect_operation.ts (popup setup awaited before the window opens)
- Firebase JS API reference: firebase/auth (signInWithPopup, signInWithRedirect, getRedirectResult, setPersistence, AuthErrorCodes)
- Firebase: Authentication State Persistence
- Firebase: Best practices for using signInWithRedirect on browsers that block third-party storage access
- Firebase: Authenticate Using Google with JavaScript
- MDN: Window: open() method
- MDN: Features gated by user activation
- HTML Standard: User interaction (transient activation duration)
- MDN: The iframe element (sandbox, allow-popups)
- FlutterFlow Community: Google Login Popup Blocked in Test/Preview Mode (May 27, 2026)
- Google for Developers Blog: Upcoming security changes to Google's OAuth 2.0 authorization endpoint in embedded webviews
How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.