Am I inside
an iframe?
One line of JavaScript answers it: window.self !== window.top. If true, your page is rendered inside an iframe; if false, you are the top document. Below: the live check running on this very page, the caveats you should know, and how to prevent framing in the first place.
The one-line check
window.self always refers to the current window. window.top always refers to the outermost window. If they are not the same object, your page is not the top one — somebody embedded you.
if (window.self !== window.top) {
console.log('We are inside an iframe');
} else {
console.log('We are the top-level page');
}That is the whole detection. Everything below is when this simple check is not enough.
Why window.top and not window.parent
window.parent points to the immediate parent. If your page is two iframes deep — frame inside a frame inside a top page — window.parent is the middle frame, not the actual top window. window.top always points to the outermost window of the browsing context, so the check works at any nesting depth.
Cross-origin access throws
The check itself never throws. But the moment you try to read window.top (its location, its document, any property other than identity comparison), the Same-Origin Policy will block you if the parent is on a different origin. Wrap any subsequent access in a try / catch:
const framed = window.self !== window.top;
let crossOrigin = false;
if (framed) {
try {
// Just touching the property is enough to trigger the SOP check.
window.top.location.href;
} catch {
crossOrigin = true;
}
}
console.log({ framed, crossOrigin });How to stop your page from being framed
If the answer to “am I in an iframe?” should always be no, do not rely on JavaScript — refuse the framing at the HTTP level. Two headers do this, and you should send both:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'X-Frame-Options: DENY is the legacy header — every modern browser still respects it. frame-ancestors in Content-Security-Policy is the replacement; it supports an allowlist (frame-ancestors 'self' https://trusted.example) and supersedes X-Frame-Options where both are present. Browsers that see either one will refuse to render the page inside an iframe at all — long before any of your JavaScript loads.
The sandbox caveat
An iframe with the sandbox attribute and no allow-same-origin is treated by the browser as a unique opaque origin, even if its URL matches its parent. From inside such a frame, window.self !== window.top still returns true as expected. But touching anything on window.top throws — because the sandbox is now, effectively, cross-origin to everything else.
For a frame to share origin with its parent it needs sandbox="allow-scripts allow-same-origin". That combination is generally a bad idea — same-origin sandbox can rewrite its own iframe attributes and break out of the sandbox — but the API allows it.
Defending the check itself
A determined embedding page that loads your script (rather than embedding your URL) can override window.top before your code runs:
// Hostile page sets this up before injecting your script:
Object.defineProperty(window, 'top', { get: () => window.self });For pages where this matters (banking, internal admin, anything one-click-destructive) do not rely on the JS check at all. Send the HTTP headers above. If you cannot send the headers — for instance, you are a script being included on someone else's page — accept that window.top is untrustworthy and verify the embedding origin via the server.
Frequently asked questions
window.parent points to the immediate parent — for an iframe nested two levels deep, parent is the middle frame, not the top. window.top always points to the outermost window, which is what 'am I framed' really asks. For one-level nesting they happen to be equal; for any deeper case window.top is correct.
Not from outside the frame — the Same-Origin Policy stops a cross-origin parent from rewriting your window.top reference. The frame itself can override window.top with a property of its own (using Object.defineProperty before any code runs), so a hostile page that loads your script could spoof the check. For high-security cases, also verify the embedding origin via document.referrer and consider rejecting framing entirely (see below).
An iframe with sandbox="allow-scripts" but no allow-same-origin is treated as a unique opaque origin, so window.self !== window.top still returns true, but window.top.location throws on cross-origin access (because the sandbox is cross-origin to its parent). The check itself is unaffected; downstream code that reaches into window.top must handle the throw.
frame-ancestors (in Content-Security-Policy) supersedes X-Frame-Options and supports a domain allowlist — for example, frame-ancestors 'self' https://trusted.example. Modern browsers all support frame-ancestors. Keep X-Frame-Options as a fallback only if you support extremely old browsers.
Because the site sends X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none'. The browser refuses to render the page inside any iframe, so the script never gets a chance to detect it. That is the recommended posture for pages that have no business being framed.