CSP for Vimeo Embeds: What to Allow and What to Avoid

Table of Contents

Embedding Vimeo looks simple until your Content Security Policy blocks it.

I’ve seen this happen a lot: the iframe is added, everything works locally, then production ships with a stricter CSP and the player turns into a blank rectangle. The fix usually isn’t “allow everything from https:” unless you enjoy undoing the point of CSP.

For Vimeo embeds, the main question is how strict you want to be while still letting the player work. There are a few workable patterns, and each has tradeoffs.

The short version

If you only use the standard Vimeo iframe embed, the key directive is usually:

frame-src https://player.vimeo.com;

That’s the minimum starting point for the iframe itself.

Depending on your page and Vimeo features, you may also need to allow related origins for scripts, media, images, or connections. The exact list depends on whether you are:

  • only embedding the iframe
  • using the Vimeo Player API
  • loading thumbnails or previews yourself
  • using a strict CSP with separate child-src, media-src, or connect-src

Why Vimeo embeds hit CSP

A Vimeo embed is usually an iframe like this:

<iframe
  src="https://player.vimeo.com/video/123456789"
  width="640"
  height="360"
  frameborder="0"
  allow="autoplay; fullscreen; picture-in-picture"
  allowfullscreen>
</iframe>

From a CSP perspective, this means your page is trying to load a frame from another origin. If your policy only allows self-hosted frames, the browser blocks it.

That’s the first and most obvious requirement:

frame-src 'self' https://player.vimeo.com;

If you don’t set frame-src, browsers may fall back to default-src, which often causes confusion. I see this in real policies all the time. For example, this header from Headertest blocks most third-party frames except Cookiebot because frame-src is explicitly narrow:

content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-MzZhMzM2ZGQtYzk0MC00M2Y3LTk3MWYtNzE4YmZhZjZlNDcz' '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://tallycdn.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 drop a Vimeo iframe into that page, it gets blocked immediately because player.vimeo.com isn’t in frame-src.

Option 1: Minimal iframe-only policy

This is the cleanest setup if you just want the standard embed and don’t need custom JavaScript integration.

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://player.vimeo.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'self';

Pros

  • Very tight and easy to reason about
  • Low blast radius if another third-party widget gets added later
  • Usually enough for plain iframe embeds

Cons

  • Won’t cover Player API integrations on your page
  • May need more directives if you fetch Vimeo assets directly
  • Can break silently if your app assumes default-src covers everything

This is my preferred baseline. Start here unless you know you need more.

Option 2: Vimeo embed with Player API support

If you use Vimeo’s JavaScript Player API, you’ll typically load their player script and communicate with the iframe. That changes the CSP shape.

Example page:

<div>
  <iframe
    id="vimeo-player"
    src="https://player.vimeo.com/video/123456789"
    allow="autoplay; fullscreen; picture-in-picture"
    allowfullscreen>
  </iframe>
</div>

<script src="https://player.vimeo.com/api/player.js"></script>
<script>
  const iframe = document.getElementById('vimeo-player');
  const player = new Vimeo.Player(iframe);

  player.on('play', () => {
    console.log('Played');
  });
</script>

Now you likely need at least:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://player.vimeo.com;
  frame-src 'self' https://player.vimeo.com;
  connect-src 'self' https://player.vimeo.com https://*.vimeo.com;
  object-src 'none';
  base-uri 'self';

Pros

  • Supports richer control over playback
  • Better UX when you need events, analytics hooks, or custom controls
  • Still fairly constrained if you scope directives carefully

Cons

  • More moving parts
  • Easier to over-allow with wildcards
  • connect-src requirements can be fiddly depending on browser behavior and Vimeo features

I’d test this in Content-Security-Policy-Report-Only first if you’re tightening an existing app. Vimeo integrations often work differently across real pages than in isolated demos.

Option 3: Broad allowlist for “make it work fast”

This is what teams often do under deadline pressure:

Content-Security-Policy:
  default-src 'self' https://*.vimeo.com;
  frame-src https://*.vimeo.com;
  script-src 'self' https://*.vimeo.com;
  img-src 'self' data: https://*.vimeocdn.com https://*.vimeo.com;
  connect-src 'self' https://*.vimeo.com;

Will it probably work? Yes.

Would I recommend it as a long-term policy? Not really.

Pros

  • Fastest path when debugging production breakage
  • Handles future subdomain shifts better
  • Lower chance of edge-case embed failures

Cons

  • Much broader trust boundary
  • Harder to audit later
  • Encourages “just wildcard it” CSP habits, which usually spread to other vendors

This is acceptable as a temporary stabilization step, not as your final destination.

Directive-by-directive comparison

frame-src

This is the big one.

Use:

frame-src https://player.vimeo.com;

Pros: precise, obvious, and usually sufficient for the iframe itself.
Cons: if you forget it, default-src fallback may block the embed and waste your afternoon.

script-src

Only needed if you load Vimeo JavaScript on your page, such as player.js.

Use:

script-src 'self' https://player.vimeo.com;

Pros: supports Player API use cases.
Cons: adding third-party script permissions always deserves scrutiny.

If your site already uses nonces and strict-dynamic, be careful. In a policy like the Headertest example, host allowlists in script-src can become less relevant once trusted nonce-bearing scripts dynamically load other scripts. That’s not inherently wrong, but you should understand the behavior before piling on exceptions.

connect-src

Needed when your page or loaded scripts make network requests.

Possible pattern:

connect-src 'self' https://player.vimeo.com https://*.vimeo.com;

Pros: fixes API/event-driven integrations that otherwise fail mysteriously.
Cons: this is often where policies get broadened more than necessary.

img-src and media-src

If you load Vimeo thumbnails, posters, or media assets directly, you may need these.

Example:

img-src 'self' data: https://i.vimeocdn.com https://*.vimeocdn.com;
media-src https://*.vimeocdn.com https://*.vimeo.com;

Pros: lets you support custom preview UI.
Cons: many teams add these preemptively without evidence they’re needed.

My rule: don’t add a directive just because it sounds plausible. Add it because a real browser violation told you to.

1. Plain Vimeo iframe embed

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://player.vimeo.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

2. Vimeo iframe + Player API

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://player.vimeo.com;
  frame-src 'self' https://player.vimeo.com;
  connect-src 'self' https://player.vimeo.com https://*.vimeo.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';

3. Existing strict site policy, adding Vimeo safely

Starting from a policy like the Headertest header, the smallest likely change is:

frame-src 'self' https://consentcdn.cookiebot.com https://player.vimeo.com;

If you also use the Vimeo Player API:

script-src 'self' 'nonce-<your-nonce>' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com https://player.vimeo.com;
frame-src 'self' https://consentcdn.cookiebot.com https://player.vimeo.com;
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com https://player.vimeo.com https://*.vimeo.com;

For ready-made policy patterns, csp-examples.com is useful when you want a starting template instead of building from scratch.

My opinionated take

For Vimeo, most sites overcomplicate CSP or over-broaden it.

The best approach is:

  1. allow https://player.vimeo.com in frame-src
  2. add script-src only if you actually load Vimeo JS
  3. add connect-src, img-src, or media-src only after seeing real violations
  4. prefer exact hosts over wide *.vimeo.com unless you have evidence you need the wildcard

That keeps the policy readable and defensible.

If you want to verify exact behavior, check Vimeo’s official player docs and embed docs before locking the policy down:

A CSP that’s too loose is barely a CSP. A CSP that’s too strict breaks video. The sweet spot for Vimeo is usually pretty small if you resist the urge to whitelist half the internet.