1. Introduction
If you are developing an iframe-based widget, embedded tool, or third-party script, you will inevitably run into a question that sounds deceptively simple:
"Which website embedded my iframe?"
Knowing the parent domain is essential for domain authorization, analytics, and security origin checks. But here is the catch: modern web browsers trust nobody — especially not your iframe.
Trying to read window.parent.location.href across origins is like peeking into your neighbor's window — the browser's security guard (DOMException) will tackle you immediately. And if you rely on document.referrer, strict no-referrer policies will ghost your script faster than a bad Tinder date.
In this guide, we will cover:
- Why
window.parent.locationfails (and why Same-Origin Policy is browser-speak for "You can look, but don't touch"). - Why
document.referrerbreaks when websites decide to go full stealth mode. - How
window.location.ancestorOriginssaves us from nested iframe Inception. - How to write a robust, production-ready Vanilla JavaScript helper function (
getTopParentDomain) that gets the domain without crashing your app.
2. Why window.parent.location Fails (Same-Origin Policy)
When your iframe page (https://my-widget.com) is embedded inside a host page (https://example.com), they are Cross-Origin (different domains).
Because they are cross-origin, the browser enforces the Same-Origin Policy (SOP). SOP is the browser security rule that basically states: "Scripts can directly access DOM properties across windows only when the relevant documents share the same origin."
If you try to bypass this:
// ❌ DOMException: Blocked a frame with origin "https://my-widget.com" from accessing a cross-origin frame.
// (Also known as: the error message that makes you question your life choices at 2 AM)
const parentUrl = window.parent.location.href;
The browser shuts it down instantly. You cannot read the parent's URL, DOM, or cookies. Security policies in 2026: Google knows who the user is, the ISP knows who the user is, but your own iframe isn't allowed to know what website it's sitting on.
3. The document.referrer Problem (Or: How Referrer Policies Ghost You)
A common first approach is document.referrer.:
console.log(document.referrer); // "https://example.com/blog/page-1"
In an ideal world, document.referrer gives you the host URL. But we don't live in an ideal world; we live in a world of privacy policies.
Why document.referrer ghosted you
Websites can strip referrer headers entirely using a meta tag:
<!-- Parent website going full stealth mode -->
<meta name="referrer" content="no-referrer">
or HTTP headers:
Referrer-Policy: no-referrer
or directly on the iframe tag:
<iframe src="https://my-widget.com" referrerpolicy="no-referrer"></iframe>
When any of these are active, document.referrer inside your iframe evaluates to an empty string (""). Your iframe enters the DOM wearing dark sunglasses and a trench coat with zero memory of where it came from.
4. The Solution: Multi-Tier Parent Domain Resolution
Since no single method works 100% of the time across all browsers, we build a 3-tier survival engine:
┌────────────────────────────────────────┐
│ getTopParentDomain() Started │
└───────────────────┬────────────────────┘
│
Is top-level window?
(window.parent === window)
/ \
YES / \ NO (Inside Iframe)
/ \
┌──────────────────────────┐ ┌───────────────────────────────────┐
│ Parse document.referrer │ │ Check ancestorOrigins[last] │
│ Or return 'Direct Access'│ │ (Chromium / WebKit - Top Domain) │
└──────────────────────────┘ └─────────────────┬─────────────────┘
│
Found origin?
/ \
YES / \ NO
/ \
┌──────────────────────────┐ ┌────────────────────────────┐
│ Return Top Main Domain │ │ Check document.referrer │
└──────────────────────────┘ └──────────────┬─────────────┘
│
Found origin?
/ \
YES / \ NO
/ \
┌──────────────────────────┐ ┌───────────────────────────┐
│ Parse Referrer Origin │ │ Start postMessage │
└──────────────────────────┘ │ Handshake (3s Timeout) │
└───────────────────────────┘
5. Handling Nested Iframes with ancestorOrigins (Escaping Iframe Inception)
What happens if someone puts your iframe inside another iframe inside another iframe? Congratulations — you've created web development Russian nesting dolls:
Topmost Main Site A (https://main-portal.com) [Address Bar]
└── Wrapper Iframe B (https://agency-host.com)
└── Your Widget C (https://my-widget.com)
If you try to inspect immediate parent origins in a nested setup, you get agency-host.com, which isn't the real website!
In Chrome, Edge, Safari, and Opera, Chromium gives us window.location.ancestorOrigins. This is an array of all parent origins up the chain:
window.location.ancestorOrigins[0]
// ➔ "https://agency-host.com" (Immediate Parent B)
window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1]
// ➔ "https://main-portal.com" (Topmost Main Site A in the Address Bar!)
ancestorOrigins[ancestorOrigins.length - 1] is your totem in Inception — it instantly wakes you up at the topmost domain in the address bar.
(Note: Chrome gives you ancestorOrigins generously. Browser support isn't universal, so we need another fallback. Because apparently Firefox would like us to mind our own business. That's why we need Tier 3).
6. The Bulletproof Handshake: Bi-Directional postMessage
When no-referrer strips document.referrer AND ancestorOrigins is unsupported (hello, Firefox!), we fall back to a bi-directional postMessage handshake.
A postMessage handshake is basically two introverted browser windows awkwardly waving at each other across cross-origin boundaries until someone confirms their origin.
The Handshake Steps:
-
Widget Mounts: The iframe posts a
WIDGET_READYsignal towindow.parent. -
Parent Script Listens: The host script catches
WIDGET_READYand posts back{ type: 'PARENT_ORIGIN_INIT' }. -
Browser Cryptography Magic: The browser automatically attaches
event.originto the message inside the iframe. The browser supplies event.origin based on the origin of the window that sent the message. JavaScript cannot arbitrarily set this value, but your application should still validate the received origin and message source before trusting the message.
7. Production-Ready Vanilla JS Solution
Here is the complete, zero-dependency, production-ready Vanilla JavaScript code.
1. Parent Page Script (Placed on Host Site)
// Placed on the host website (or bundled into your embed script)
window.addEventListener('message', function(event) {
// Respond only when widget notifies it is ready
if (event.data && event.data.type === 'WIDGET_READY') {
if (event.source) {
event.source.postMessage({ type: 'PARENT_ORIGIN_INIT' }, '*');
}
}
});
2. The Iframe Domain Helper (getTopParentDomain)
/**
* Helper to extract topmost domain from Chromium ancestorOrigins
*/
function getTopmostAncestorDomain() {
if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
const topAncestor = window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1];
if (topAncestor && topAncestor !== 'null') {
return topAncestor;
}
}
return null;
}
/**
* Detects the available embedding origin, with the top-level ancestor available when ancestorOrigins is supported.
* @returns {Promise<string>} Resolves to the detected parent domain (e.g. "https://example.com")
*/
function getTopParentDomain() {
if (typeof window === 'undefined') {
return Promise.resolve('Server');
}
const isEmbedded = window.parent !== window;
// 1. Direct Browser Access (Not inside an iframe)
if (!isEmbedded) {
if (document.referrer) {
try {
return Promise.resolve(new URL(document.referrer).origin);
} catch (e) {
return Promise.resolve(document.referrer);
}
}
return Promise.resolve('Direct Access');
}
// 2. Try Chromium/WebKit ancestorOrigins immediately
const ancestorDomain = getTopmostAncestorDomain();
if (ancestorDomain) {
return Promise.resolve(ancestorDomain);
}
// 3. Try document.referrer immediately
if (document.referrer) {
try {
const referrerDomain = new URL(document.referrer).origin;
if (referrerDomain && referrerDomain !== 'null') {
return Promise.resolve(referrerDomain);
}
} catch (e) {
// Ignore URL parse error
}
}
// 4. Fallback: Initiate postMessage Handshake (for strict no-referrer policies)
return new Promise(function(resolve) {
let resolved = false;
function cleanupAndResolve(domain) {
if (resolved) return;
resolved = true;
clearTimeout(timeoutId);
window.removeEventListener('message', handleHandshake);
resolve(domain);
}
function handleHandshake(event) {
if (event.data && event.data.type === 'PARENT_ORIGIN_INIT') {
if (event.origin && event.origin !== 'null') {
// Prefer ancestorOrigins if available; fallback to browser-verified event.origin
const topDomain = getTopmostAncestorDomain() || event.origin;
cleanupAndResolve(topDomain);
}
}
}
// Listen for parent handshake response
window.addEventListener('message', handleHandshake);
// Notify parent window that widget is ready
window.parent.postMessage({ type: 'WIDGET_READY' }, '*');
// Timeout safety fallback (3 seconds: enough for slow sites without blocking forever)
const timeoutId = setTimeout(function() {
cleanupAndResolve('Unknown');
}, 3000);
});
}
// Example Usage:
getTopParentDomain().then(function(domain) {
console.log('Detected Parent Domain:', domain);
});
8. Summary Checklist for Developers
| Scenario | Detection method | Result |
|---|---|---|
| Supported browser + nested iframe | ancestorOrigins[last] |
Top-level ancestor origin |
| Cross-origin iframe + referrer available | document.referrer |
Referring origin |
| Referrer unavailable | postMessage |
Immediate parent's origin |
| Direct page access | document.referrer |
Referring origin or empty |
Conclusion
So, what started as a simple question — "Which website embedded my iframe?" — turns out to involve a few browser security rules, privacy policies, and enough iframe nesting to make you question your life choices.
The practical approach is to use the browser information available to you:
- Use
ancestorOrigins[last]when you need the top-level ancestor origin and the browser supports it. - Use
document.referrerwhen referrer information is available. - Use a
postMessagehandshake when the parent page can explicitly cooperate and provide its origin. - If none of these methods can provide the information, return a safe fallback instead of pretending the browser owes you an answer.
The important part is that there is no universal way for a cross-origin iframe to freely inspect its parent's location. That's not a missing JavaScript API. That's the browser's security model doing exactly what it was designed to do.
So the next time your iframe asks, "Who is my parent?", at least now you have a few ways to investigate before calling it a family issue.
Happy coding!




Top comments (0)