All guides

Allow embedding on Netlify

Add a Content-Security-Policy header with frame-ancestors in a _headers file or in netlify.toml, and remove X-Frame-Options. These rules apply only to static files. Pages from functions, edge functions, or SSR must set the header in code.

4 min read · Updated

Option 1: the _headers file

Create a plain text file named _headers in your publish directory, for example dist/ or public/:

/*
  Content-Security-Policy: frame-ancestors 'self' https://partner.example

The first line is the path. The indented lines below it are the headers. /* matches every path.

Some site generators skip files that start with _. If the header does not show up, check that _headers is in the build output.

Option 2: netlify.toml

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "frame-ancestors 'self' https://partner.example"

Use one of the two files for headers, not both. Then you have one place to look.

In both files, delete any X-Frame-Options line. Starter templates often include X-Frame-Options: DENY.

Allow framing on one path only

Give each section of the site its own rule, and keep the paths apart:

/
  Content-Security-Policy: frame-ancestors 'self'

/app/*
  Content-Security-Policy: frame-ancestors 'self'

/embed/*
  Content-Security-Policy: frame-ancestors https://partner.example

Do not put a site-wide /* CSP rule next to an /embed/* rule. Both rules match /embed/ pages. Netlify does not document how the two values combine. A page with two CSP headers must pass both policies, so 'self' blocks the partner.

SSR pages and functions: set the header in code

Netlify applies custom headers only to files it serves from its own storage. Responses from Netlify Functions, edge functions, proxies, and server-rendered pages do not get them. This includes Next.js, Astro, Remix, and Nuxt pages that render on the server.

For those, set the header where the response is built. Or change it on the way out with an edge function:

// netlify/edge-functions/frame-headers.ts
import type { Config, Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  const response = await context.next();
  const result = new Response(response.body, response);
  result.headers.delete("X-Frame-Options");
  result.headers.set("Content-Security-Policy", "frame-ancestors 'self' https://partner.example");
  return result;
};

export const config: Config = { path: "/embed/*" };

headers.set replaces any CSP the page sent. If the page had other CSP directives, copy them into this value.

Check the result

Deploy, then read the headers:

curl -sI https://your-site.com/embed/ | grep -iE "x-frame-options|content-security-policy"

You want one CSP line with your frame-ancestors and no X-Frame-Options. Test a static page and a server-rendered page. They follow different rules.

Common mistakes

  • Rules in _headers for an SSR page. They have no effect there. Use code or an edge function.
  • _headers in the wrong folder. It must be in the publish directory after the build.
  • ALLOW-FROM. Browsers ignore it, so every site can frame the page.
  • CSP in a <meta> tag. frame-ancestors does not work there.

Test the live site

Paste the deployed URL into the iframe tester. It tells you if the page can load in a frame and which header blocks it.

To check many pages at once, use the bulk check. It shows X-Frame-Options and frame-ancestors for up to 100 URLs.

Sources

Check your page now

Know before you embed.

Free, no sign-up, nothing stored. Paste a URL and get a verdict, a live preview, and the fix.