Also available in: Español · Português · Français · العربية
X-Frame-Options: what the header really does now
Paste your header and the frames around it, and see which step of the standard decides.
What is X-Frame-Options?
X-Frame-Options is a response header that tells a browser whether your page may be displayed inside someone else's frame. It exists to stop clickjacking: an attacker embeds your real page, makes it invisible, and lets a victim click a button they cannot see. Two values are meaningful — DENY refuses all framing, and SAMEORIGIN allows it only from your own origin.
The header is older than the process that usually produces web standards. Browsers shipped it around 2009, and the description everyone cites, RFC 7034, was written four years later to document what had already been built. That RFC is marked Informational, and says so about itself in its opening lines: it is not an Internet Standards Track specification.
Which means the document most pages link to is no longer the one that governs behaviour. The HTML Standard now carries the processing model and states plainly that its definition supersedes the RFC. The grammar it gives is shorter than the RFC's, the algorithm is more precise, and it settles several questions the RFC explicitly left open.
How to use it
- Enter your header. One per line if the response sends more than one. A comma-separated value and several separate headers are normatively equivalent, so either form gives the same answer here.
- Describe the frames around the page. Give the origin of the document sending the header, then the origins framing it, innermost first, up to the browser tab. This matters more than it looks: the check walks the entire chain.
- Add a Content-Security-Policy if you send one. If an enforced policy carries frame-ancestors, X-Frame-Options is ignored outright. Tick the Report-Only box to see the difference that makes, which is larger than most people expect.
The cases no reference table lists
The interesting behaviour is what happens when a response carries more than one value, which is common in practice because a proxy or a framework adds a header the application already set. The standard publishes a table of worked answers for exactly these cases, and the results are not what you would guess. SAMEORIGIN beside an invalid value blocks the page. Two invalid values allow it. The rule is deliberate: a header carrying one meaningful value and one confused one looks like a misconfiguration of something that was trying to work, so it is blocked on purpose, while a header that is entirely unparseable is treated as though it were never sent.
A trailing comma is enough to trigger it. Splitting on commas turns SAMEORIGIN, into two values, the second one empty, and that counts as a second distinct value — so the page is blocked. Sending SAMEORIGIN in one header and an empty X-Frame-Options in another does the same thing, for exactly the same reason.
ALLOWALL is the strangest survivor. It appears nowhere in RFC 7034 — it was never part of the header's documented grammar — yet the standard names it, purely so that it can be caught by the multiple-value rule. On its own it does nothing at all, and a page sending only ALLOWALL is as embeddable as one sending no header.
ALLOW-FROM is the one people still copy from tutorials. It is in the RFC's grammar, and the HTML Standard says in as many words that it is not to be implemented. A page relying on it to permit one specific partner is permitting everybody, silently, because a value the grammar does not admit is treated as no header at all.
The question the RFC left open
SAMEORIGIN sounds unambiguous and is not. Consider a page framed by a partner's widget, where that widget is itself framed by your own site at the top. Is the origin being compared the page immediately doing the framing, the top-level document in the tab, or every document in between? RFC 7034 admits all three readings in its own text, noting that the criteria may not be evaluated unanimously and listing each possibility.
The HTML Standard settles it: the check walks up the entire ancestor chain, and a single cross-origin document anywhere in it blocks the page. So the example above is refused, even though the top-level page is yours. That is the strictest of the three readings, and it is the one the tool above implements — which is why the default example looks as though it ought to be allowed and is not.
The other rule worth knowing is that a CSP frame-ancestors directive does not merely take priority, it removes X-Frame-Options from consideration entirely. The algorithm checks for the directive before it looks at the header's value at all, so a page sending DENY alongside any policy containing frame-ancestors is governed only by the policy. But this holds only for an enforced policy. A Content-Security-Policy-Report-Only header is skipped, because the algorithm considers only policies whose disposition is enforce — so moving frame-ancestors into Report-Only while you test it quietly hands control back to a header you may have stopped thinking about.
What this cannot tell you
It tells you what a conformant browser must do, not what any particular browser does. That distinction is real: the whole reason the RFC concedes an ambiguity is that implementations differed, and a browser can carry old behaviour for years. Nothing here has been measured against a shipping browser, and the browser support appendix in a document from 2013 is not evidence about today.
It also cannot see the page you are asking about. You supply the origins, so an answer is only as good as the chain you described — and the chain is exactly the part that is easy to get wrong, since a page can be framed by something you did not put there. If you want the real answer for a real page, the browser's own developer tools will tell you when a frame was refused, and why.
Finally, X-Frame-Options is the older half of a pair. Anything you can express with it, you can express with a CSP frame-ancestors directive, which also handles the cases the header cannot: allowing a specific origin, allowing several, or matching a scheme. The header is still worth sending for old clients, and the two coexist perfectly well, but a new deployment should be led by the policy rather than the header.
Why is it free?
Running an algorithm from a specification is arithmetic over a few strings, and it happens in your browser. There is no server in the loop, so there is nothing to bill for and no account to create.
Nothing is uploaded. The header and the origins you type never leave the tab.