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-src for a plain Spotify iframe embed
  • If your CSP is based on default-src 'self', you must explicitly allow Spotify in frame-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-src controls what your page can embed
  • frame-ancestors controls 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:

  1. Does the iframe src start with https://open.spotify.com/embed/?
  2. Does the CSP include frame-src https://open.spotify.com?
  3. Is there a second CSP header overriding the first one?
  4. Is a meta CSP tag conflicting with the response header?
  5. Are you testing an old cached policy?
  6. 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.

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.