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, orconnect-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-srccovers 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-srcrequirements 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.
Recommended policies by use case
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:
- allow
https://player.vimeo.cominframe-src - add
script-srconly if you actually load Vimeo JS - add
connect-src,img-src, ormedia-srconly after seeing real violations - prefer exact hosts over wide
*.vimeo.comunless 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.