SharePointVault ~/gotchas/spfx ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ window-open-noopener-returns-null.md
sharepoint-vault›gotchas›spfx›window-open-noopener-returns-null.mdhistoryedit on GitHub
● GOTCHAspfx· 2 min read

window.open(…, 'noopener') returns null even when the tab opened — so your fallback runs every time

applies to: Any browser code, SPFx web parts and extensions included (Outlook on the web links, print windows, "open in new tab")reviewed: 2026-09-24#spfx#browser#window-open#popups#email#print

With noopener (or noreferrer) in the features string, window.open returns null by specification — also when the window did open. Code that reads null as "the popup was blocked" runs its fallback every single time: the e-mail opens twice, the print window stays empty. When you need to know whether the window opened, drop the feature and cut the link yourself: const w = window.open(url, '_blank'); if (w) w.opener = null;.

S noopener (nebo noreferrer) ve features vrací window.open podle specifikace null – i když okno otevřel. Kód, který null čte jako „popup zablokovaný“, spustí náhradní cestu pokaždé: e-mail se otevře dvakrát a tiskové okno zůstane prázdné. Když potřebuješ vědět, jestli se okno otevřelo, feature vynech a vazbu přeruš sám: const w = window.open(url, '_blank'); if (w) w.opener = null;.

Symptom

Two features in a shared document-actions package, both "working" in a quick test:

  • "Send by e-mail" opened an Outlook on the web compose window — and immediately afterwards the local mail client via mailto:. Every user got the message twice.
  • "Print" for a comparison and a permissions report opened a new window and left it blank.

Cause

The HTML specification's window open steps end with "If noopener is true or noreferrer is true, then return null." The window opens; the caller just never gets a handle to it. So:

let win: Window | null = null;
try { win = window.open(owaComposeUrl, '_blank', 'noopener'); } catch { /* blocked */ }
if (!win) openMailto(item);            // null every time → mailto opens as well

const w = window.open('', '_blank', 'noopener,width=760,height=900');
if (!w) return;                        // returns every time → nothing is written into the window
w.document.write(html);

noopener is usually added for a good reason (the opened page must not reach back through window.opener), which is why it survives review.

Fix

Open without the feature and cut the back-reference yourself:

export function openInNewTab(url: string): boolean {
  const w = window.open(url, '_blank');
  if (!w) return false;          // really blocked
  w.opener = null;               // same protection noopener gave you
  return true;
}

if (!openInNewTab(owaComposeUrl)) openMailto(item);

For a print window, open about:blank the same way, keep the handle, write the document, then call print(). If you genuinely don't need the handle, noopener is fine — just don't put an if (!w) after it.

Notes

  • A blocked window must still be admitted to the user ("Your browser blocked the new window"), not swallowed. In the Teams webview window.open can be blocked even when called synchronously from a click.
  • Put the helper in one place. The trap went away in the apps that had it and survived in a shared package — a lint rule or guard that scans only the app's own src/ never looks inside node_modules, so a package needs its own scan.
  • Related: A mailto: fallback reported as sent — the reporting side of fallbacks.
Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-09-24last updated: 2026-09-24historyedit on GitHubview source