All guides

Allow embedding on WordPress

WordPress core blocks framing only on wp-admin and the login page, not on posts and pages. If a post is blocked, a security plugin, your host, or the web server adds the header. Turn it off at the source, then send CSP frame-ancestors.

5 min read · Updated

What WordPress core does

Core calls send_frame_options_header() on admin_init and login_init. On those screens it sends:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

The current code sends both lines. Keep this on. It protects wp-admin and wp-login.php from clickjacking. Do not try to frame the admin or login screens on another site.

Front-end pages, such as posts, pages, and archives, get no framing header from core.

Find the real source

Read the headers of a blocked page:

curl -sI https://your-site.com/sample-page/ | grep -iE "x-frame-options|content-security-policy"

Then check these places, in this order:

  1. Security plugins. Many have a "security headers" or "clickjacking" setting that adds X-Frame-Options. Turn off that one setting, or switch it to allow framing.
  2. Your host. Managed WordPress hosts often add security headers at the server or CDN. Look for a setting in the host dashboard, or ask support.
  3. The web server. Search .htaccess and the server config. See Apache or Nginx.
  4. A CDN. Cloudflare can add the header too. See Allow embedding on Cloudflare.

A quick test: turn off plugins one at a time on a staging copy. Read the headers after each change.

Set frame-ancestors in PHP

If the header comes from WordPress or a plugin, you can change it in PHP. Create a must-use plugin at wp-content/mu-plugins/frame-headers.php:

<?php
/**
 * Plugin Name: Frame headers
 */

add_action( 'send_headers', function () {
    header_remove( 'X-Frame-Options' );
    header( "Content-Security-Policy: frame-ancestors 'self' https://partner.example" );
}, 99 );
  • send_headers runs in the front-end WordPress setup only. The admin and login scripts do not run it, so those screens keep the core protection.
  • Priority 99 makes it run after most plugins.
  • header() replaces an earlier CSP that PHP set in the same request. If a plugin sent a full CSP, copy its other directives into this line.
  • header_remove() only removes headers that PHP set. It cannot remove a header that Apache, Nginx, or a CDN adds later.

To limit framing to some pages, add a check inside the function, for example if ( ! is_page( 'booking' ) ) { return; }.

The wp_headers filter also works. It receives the headers that WordPress builds in its own list, so a plugin that calls header() directly skips that filter. With send_headers and header_remove(), you also remove headers that plugins set earlier with header(). If a plugin adds the header at a later hook, turn off that plugin setting.

Set it in .htaccess

On Apache hosts, you can set the headers in the .htaccess file in the WordPress root. Put the lines above the # BEGIN WordPress block. WordPress can rewrite the lines inside that block.

<IfModule mod_headers.c>
    Header unset X-Frame-Options
    Header always unset X-Frame-Options
    Header always set Content-Security-Policy "frame-ancestors 'self' https://partner.example"
</IfModule>

This overrides headers from PHP too, including those for wp-admin. To keep the admin protected, use the PHP method, or wrap the lines in <If "%{REQUEST_URI} !~ m#^/wp-(admin|login)#">.

Common mistakes

  • Framing the login page. Core blocks it on purpose. Log users in with a popup, not a frame. See Login doesn't stick in an iframe.
  • Two CSP headers. A plugin sends one and .htaccess adds another. The browser enforces both.
  • A page cache. Caching plugins and hosts can keep old headers. Purge the cache after each change.
  • ALLOW-FROM. Some old plugins still offer it. Browsers ignore it.

Check a real post

Paste a post URL into the iframe tester. Test a page and a post. They can get different headers from different rules.

To check many pages at once, use the bulk check. It shows X-Frame-Options and frame-ancestors for up to 100 URLs.

Sources

Check your page now

Know before you embed.

Free, no sign-up, nothing stored. Paste a URL and get a verdict, a live preview, and the fix.