Spotify embeds are simple until your CSP blocks them.
You paste the iframe, reload the page, and get a blank box or a browser console full of Refused to frame errors. I’ve hit this enough times that I keep a tiny checklist for it. This guide is that checklist, with policies you can copy-paste.
If you only need the shortest possible answer:
- Spotify embeds need
frame-src https://open.spotify.com - If your page itself is allowed to be embedded nowhere, keep
frame-ancestors 'none' - You usually do not need to loosen
script-srcfor a plain Spotify iframe embed - If your CSP is based on
default-src 'self', you must explicitly allow Spotify inframe-src
The basic Spotify embed HTML
A typical Spotify embed looks like this:
<iframe
src="https://open.spotify.com/embed/track/7ouMYWpwJ422jRcDASZB7P"
width="100%"
height="152"
frameborder="0"
allowfullscreen=""
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy">
</iframe>
The key part for CSP is the src:
https://open.spotify.com/embed/...
That means your page needs to allow Spotify as a frame source.
Minimum CSP for Spotify embeds
If your site already has a strict CSP, this is the minimum change I’d make:
Content-Security-Policy: default-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'
If you prefer a meta tag for quick testing:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'">
That’s enough for the iframe itself.
If you already have a real CSP header
Most production sites don’t start from scratch. They already have a policy with analytics, consent tools, and a bunch of existing directives.
Here’s the real CSP header you provided, reformatted:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-MDg4NjZmNzgtYTU2YS00YmY3LTg4MWItOGRhYWJmODY4MGJl' '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'
To support Spotify embeds, I’d change only frame-src:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-MDg4NjZmNzgtYTU2YS00YmY3LTg4MWItOGRhYWJmODY4MGJl' '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://open.spotify.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
That’s the safe change. Don’t broaden default-src just to make one iframe work. I see that mistake a lot.
Copy-paste policies
1. Strict site, Spotify embed only
Good default if you don’t need third-party JS from Spotify:
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://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
2. Existing app with nonce-based scripts
If your app already uses nonces and strict-dynamic, just add Spotify to frame-src:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m123' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
3. Report-only rollout first
This is how I usually test CSP changes in production without breaking the page:
Content-Security-Policy-Report-Only: default-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; report-to default-endpoint
If you want background on report-only mode and CSP directives, the official CSP docs are the right place to start, and csp-guide.com is useful when you want a practical explanation.
Common mistakes
Forgetting frame-src
This is the big one.
If your policy has:
default-src 'self'
and nothing else for frames, the Spotify iframe is blocked because frame-src falls back to default-src.
You’ll see an error like:
Refused to frame 'https://open.spotify.com/' because it violates the following Content Security Policy directive: "default-src 'self'".
Fix:
frame-src 'self' https://open.spotify.com
Changing script-src for no reason
For a normal Spotify iframe embed, your page is not loading Spotify JavaScript into your origin. The browser is embedding a remote document in an iframe.
So this is usually not needed:
script-src 'self' https://open.spotify.com
I wouldn’t add it unless you actually know your page is loading scripts from Spotify directly.
Confusing frame-src with frame-ancestors
These do different jobs:
frame-srccontrols what your page can embedframe-ancestorscontrols who can embed your page
So this is valid and common:
frame-src https://open.spotify.com; frame-ancestors 'none'
That means:
- your page may embed Spotify
- nobody may embed your page
That’s often exactly what you want.
Official reference for this lives in the CSP spec and MDN docs.
Using child-src only
Older CSP setups sometimes use child-src. Modern policies should prefer frame-src for frames.
If you have legacy config like this:
child-src https://open.spotify.com
I’d update it to:
frame-src https://open.spotify.com
Example: Express / Node.js
If you’re setting headers in Express:
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
[
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'self' https://open.spotify.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'"
].join("; ")
);
next();
});
If you use Helmet, same idea:
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
frameSrc: ["'self'", "https://open.spotify.com"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
objectSrc: ["'none'"]
}
})
);
Example: Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" always;
Example: Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'"
Debugging checklist
When Spotify embeds fail, I check these in order:
- Does the iframe
srcstart withhttps://open.spotify.com/embed/? - Does the CSP include
frame-src https://open.spotify.com? - Is there a second CSP header overriding the first one?
- Is a meta CSP tag conflicting with the response header?
- Are you testing an old cached policy?
- Is some extension messing with the page?
Browser console errors usually tell you exactly which directive blocked the load. Read the full error. Half the time the answer is right there.
Recommended production policy snippet
If you just want the one snippet I’d ship for a page with Spotify embeds:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'self' https://open.spotify.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
Replace {RANDOM} with your actual per-request nonce.
That keeps the policy tight and allows the one thing you actually need: the Spotify iframe.
Official docs
For the official reference, check the Spotify embed documentation and the CSP documentation from MDN / browser vendors. For practical directive breakdowns, csp-guide.com is a handy companion when you’re trying to decode why a policy behaves the way it does.