Facebook video embeds are one of those things that look trivial until CSP starts blocking them.

You paste Facebook’s embed code, reload, and suddenly your console is full of Refused to frame or Refused to load the script errors. The fix is usually small, but the exact directives matter. If you loosen the wrong thing, you end up with a policy that “works” and quietly stops protecting anything useful.

Here’s the practical reference I wish more teams had handy.

The short version

For a basic Facebook video embed, you’ll usually need to allow:

  • frame-src for Facebook’s embed/player origins
  • script-src if you use Facebook’s SDK-based embed
  • connect-src in some SDK cases
  • img-src if Facebook assets are fetched
  • possibly style-src if your page or widget injects inline styles

If you’re embedding with a plain <iframe>, start with frame-src. That’s the cleanest option.

Option 1: Facebook video embed via iframe

This is the easiest setup to secure.

Typical embed code looks like this:

<iframe
  src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2Ffacebookapp%2Fvideos%2F10153231379946729%2F&show_text=0&width=560"
  width="560"
  height="315"
  style="border:none;overflow:hidden"
  scrolling="no"
  frameborder="0"
  allowfullscreen="true"
  allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share">
</iframe>

For this style of embed, a minimal CSP often looks like:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://www.facebook.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';

Why this works

  • frame-src https://www.facebook.com allows the iframe itself
  • img-src https: covers remote images if your page loads previews or related assets
  • style-src 'unsafe-inline' is only there because your iframe example uses an inline style="" attribute

If you remove the inline style attribute from the iframe, you can tighten that up:

<iframe
  class="fb-video-frame"
  src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2Ffacebookapp%2Fvideos%2F10153231379946729%2F&show_text=0&width=560"
  width="560"
  height="315"
  scrolling="no"
  frameborder="0"
  allowfullscreen="true"
  allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share">
</iframe>
.fb-video-frame {
  border: none;
  overflow: hidden;
}

Then your CSP can become:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://www.facebook.com;
  img-src 'self' data: https:;
  style-src 'self';
  object-src 'none';
  base-uri 'self';

That’s better. I always prefer removing inline style allowances when I can.

Option 2: Facebook embed using the SDK script

Some teams use Facebook’s JavaScript SDK instead of a raw iframe.

Example markup:

<div id="fb-root"></div>
<script async defer crossorigin="anonymous"
  src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v19.0">
</script>

<div class="fb-video"
  data-href="https://www.facebook.com/facebookapp/videos/10153231379946729/"
  data-width="560"
  data-allowfullscreen="true">
</div>

For this, you need more than frame-src.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://connect.facebook.net;
  frame-src 'self' https://www.facebook.com;
  connect-src 'self' https://connect.facebook.net https://www.facebook.com;
  img-src 'self' data: https:;
  style-src 'self';
  object-src 'none';
  base-uri 'self';

This is the version people usually miss: they allow the frame, but forget the SDK script is loaded from connect.facebook.net.

Copy-paste policies

Minimal CSP for iframe-only Facebook video embeds

Use this if the page only needs the iframe embed and no Facebook SDK.

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://www.facebook.com;
  object-src 'none';
  base-uri 'self';

That’s the smallest starting point. If your page also loads external images, fonts, or styles, add those separately.

Safer practical CSP for a normal page with iframe embeds

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self';
  frame-src 'self' https://www.facebook.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

I like this shape because it stays explicit. default-src is not a substitute for setting the directives you actually care about.

CSP for Facebook SDK + video embeds

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://connect.facebook.net;
  style-src 'self';
  img-src 'self' data: https:;
  connect-src 'self' https://connect.facebook.net https://www.facebook.com;
  frame-src 'self' https://www.facebook.com;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

Nonce-based CSP with your own scripts

If your app already uses nonces, keep doing that. Don’t fall back to 'unsafe-inline' for convenience.

Example HTML:

<script nonce="{{ .CSPNonce }}">
  window.videoEmbedEnabled = true;
</script>

<script nonce="{{ .CSPNonce }}" async defer crossorigin="anonymous"
  src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v19.0">
</script>

Matching CSP:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{RANDOM_NONCE}}' https://connect.facebook.net;
  style-src 'self';
  img-src 'self' data: https:;
  connect-src 'self' https://connect.facebook.net https://www.facebook.com;
  frame-src 'self' https://www.facebook.com;
  object-src 'none';
  base-uri 'self';

If you use nonce-based CSP heavily, read up on directive behavior at csp-guide.com. The difference between “works in dev” and “holds up in production” usually comes down to understanding fallback behavior and script trust propagation.

Real-world example: adding Facebook to an existing CSP

Here’s a real CSP header from headertest.com:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-ZGU2Y2YyOTgtMWVmYi00NTFjLWIzMDgtMDQ3M2EzNWNkN2Qx' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://collect.tallytics.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'

If you wanted to support a Facebook video iframe on a site with that policy, the smallest likely change is:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-ZGU2Y2YyOTgtMWVmYi00NTFjLWIzMDgtMDQ3M2EzNWNkN2Qx' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://collect.tallytics.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com https://www.facebook.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'

If you also load the Facebook SDK script, then add https://connect.facebook.net to script-src, and probably connect-src too.

That’s the pattern I use in production: make the smallest possible change, verify exactly what was blocked, then expand only if the browser proves you need it.

Common CSP errors and fixes

Refused to frame 'https://www.facebook.com/' because it violates frame-src

You need:

frame-src https://www.facebook.com

If you only set default-src, browsers may still block the frame depending on your policy shape and fallback behavior.

Refused to load the script 'https://connect.facebook.net/...'

You need:

script-src https://connect.facebook.net

Or add it to your existing script-src list.

Refused to connect to https://www.facebook.com/...

Usually SDK-related. Add:

connect-src https://www.facebook.com https://connect.facebook.net

Inline style blocked on the iframe

If your embed markup includes:

style="border:none;overflow:hidden"

Then either:

  1. allow inline styles:
style-src 'self' 'unsafe-inline'

or, better:

  1. move styles into your stylesheet and keep:
style-src 'self'

I’d pick option 2 every time unless you’re stuck inside a CMS that insists on inline junk.

A few gotchas

frame-src vs child-src

Use frame-src. Older docs and examples sometimes mention child-src, but for iframe control, frame-src is the one you want in modern policies.

Don’t whitelist all of Facebook unless you have to

This is sloppy:

frame-src https://*.facebook.com

Maybe you need it. Usually you don’t. Start with:

frame-src https://www.facebook.com

Same for scripts. Don’t spray wildcards around because one blog post said so in 2019.

X-Frame-Options does not control what your page can embed

I still see this confusion. X-Frame-Options controls whether your page can be framed by someone else. It does not allow Facebook embeds. That’s frame-src.

If I were adding Facebook video embeds to a reasonably locked-down site today, I’d start here for iframe-only embeds:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self';
  frame-src 'self' https://www.facebook.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

And for SDK-based embeds:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://connect.facebook.net;
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://connect.facebook.net https://www.facebook.com;
  frame-src 'self' https://www.facebook.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

That gets you a working baseline without turning CSP into decorative security.