Embedding YouTube looks simple right up until CSP starts blocking half of it.
You add an <iframe>, refresh the page, and suddenly the browser console is yelling about frame-src, img-src, or some random googlevideo.com request you didn’t explicitly ask for. That’s normal. YouTube embeds pull in more than just youtube.com, and playlists add a couple more moving parts.
Here’s the practical reference I wish I had the first few times I locked this down.
The short version
For a basic YouTube embed, you usually need:
frame-srcfor the iframe itselfimg-srcfor thumbnails and related images- sometimes
script-srcif you use the YouTube Iframe API - sometimes
connect-srcif your page code talks to YouTube or related endpoints
The core domains are usually:
https://www.youtube.comhttps://www.youtube-nocookie.comhttps://i.ytimg.comhttps://img.youtube.com
If you use privacy-enhanced mode, prefer youtube-nocookie.com.
Minimal CSP for a normal YouTube embed
If your page already has a sensible baseline policy, this is the smallest useful addition:
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
And the HTML:
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video player"
loading="lazy"
allowfullscreen>
</iframe>
That’s enough for many cases.
CSP for privacy-enhanced mode
If you use YouTube’s privacy-enhanced embed URLs, swap in youtube-nocookie.com. I recommend this by default unless you have a reason not to.
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
Embed example:
<iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
title="YouTube video player"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen>
</iframe>
If you want the least annoying setup for marketing sites, docs sites, and blogs, this is usually it.
CSP for YouTube playlists
Playlists don’t need a fundamentally different CSP. The iframe still comes from YouTube or YouTube No-Cookie. The main difference is the embed URL.
Playlist by playlist ID
<iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/videoseries?list=PL1234567890ABCDEFG"
title="YouTube playlist player"
loading="lazy"
allowfullscreen>
</iframe>
CSP:
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
Single video with a playlist context
<iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ?list=PL1234567890ABCDEFG"
title="YouTube playlist video"
loading="lazy"
allowfullscreen>
</iframe>
Same policy.
If you use the YouTube Iframe API
This is where people usually overshoot and allow too much.
If you load the API script:
<script src="https://www.youtube.com/iframe_api"></script>
<div id="player"></div>
<script nonce="{{ .CSPNonce }}">
function onYouTubeIframeAPIReady() {
new YT.Player('player', {
videoId: 'dQw4w9WgXcQ',
playerVars: {
listType: 'playlist',
list: 'PL1234567890ABCDEFG'
}
});
}
</script>
Your CSP needs to allow:
- the script from
www.youtube.com - the frame source
- thumbnails/images
- your inline script via nonce or hash
Example:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123' https://www.youtube.com;
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
If you’re still allowing 'unsafe-inline' in script-src, that’s the part I’d fix first. Use a nonce or hash instead. If you want a deeper breakdown of nonce-based policies and directive behavior, csp-guide.com is a good reference.
A stricter real-world starting point
A lot of production CSPs start from an existing locked-down policy and add YouTube on top. For example, this real policy from HeaderTest is already pretty tight:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-NzU4NDliYmMtZDU2Ni00MjMxLWI4NGEtMDdkZTFhNDRjNjU4' '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'
To support YouTube embeds there, I’d minimally change frame-src like this:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NzU4NDliYmMtZDU2Ni00MjMxLWI4NGEtMDdkZTFhNDRjNjU4' '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 https://www.youtube.com https://www.youtube-nocookie.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
That works because img-src 'self' data: https: already allows YouTube thumbnails over HTTPS. If you want to inspect a policy and catch obvious mistakes quickly, HeaderTest is handy.
Copy-paste policies
1. Basic YouTube embed
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
2. Privacy-enhanced embed
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
3. Support both normal and no-cookie embeds
Content-Security-Policy:
default-src 'self';
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
4. YouTube Iframe API with nonce
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-REPLACE_WITH_NONCE' https://www.youtube.com;
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com;
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
object-src 'none';
base-uri 'self';
Common CSP errors and what they mean
Refused to frame ... because it violates frame-src
Your iframe domain isn’t allowed.
Fix:
frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com;
Refused to load the image ... because it violates img-src
Usually thumbnail URLs from i.ytimg.com or img.youtube.com are blocked.
Fix:
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
Refused to load the script 'https://www.youtube.com/iframe_api'
You’re using the JS API but didn’t allow the script source.
Fix:
script-src 'self' https://www.youtube.com 'nonce-REPLACE_WITH_NONCE';
The embed works locally but fails in production
Classic reasons:
- production CSP is stricter than your local setup
- an edge/CDN layer injects or rewrites headers
- you tested a normal embed locally and switched to
youtube-nocookie.comlater - your page has
frame-srcbut also a legacychild-srcrule causing confusion in older setups
Don’t forget frame-ancestors
This one doesn’t control your ability to embed YouTube. It controls who can embed your page.
If you don’t want your page framed elsewhere:
frame-ancestors 'none';
If your app needs to be embedded by a parent app, configure that explicitly. I’ve seen teams accidentally break partner integrations by cargo-culting frame-ancestors 'none' into everything.
My default recommendation
If I’m adding YouTube to a typical content site, I start here:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-REPLACE_WITH_NONCE';
style-src 'self';
img-src 'self' data: https://i.ytimg.com https://img.youtube.com;
frame-src 'self' https://www.youtube-nocookie.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
Then I widen only if the implementation really needs it.
That’s the pattern that keeps CSP manageable: start strict, add the exact YouTube sources you actually use, and don’t whitelist half of Google just because one iframe looked noisy in DevTools.