Allow trusted iframe embedding on a custom domain
This advanced configuration affects your domain's security posture. Your organization is responsible for correctly configuring any infrastructure that frames Ploy content. Ploy can recommend secure practices, but cannot control external infrastructure; proceed only if your team understands and accepts the security implications.
Important: ploy.build preview URLs intentionally prevent framing. After publishing to a custom domain, your Astro site can define the approved parent origin with its own frame-ancestors policy.
Target policy:
Content-Security-Policy: frame-ancestors https://trusted-parent.example;
Start with this prompt
Replace https://trusted-parent.example with the exact origin that should be allowed to frame your site, then paste the prompt into Ploy.
Update this Astro site so it can be embedded only by this trusted parent origin:
https://trusted-parent.example
Requirements:
- The site will be published on my custom domain. Verify the final custom-domain URL, not a ploy.build preview URL.
- Set this HTTP response header on every route that must be embeddable:
Content-Security-Policy: frame-ancestors https://trusted-parent.example;
- Ploy-created Astro sites do not include X-Frame-Options by default. Check for and remove any conflicting value introduced by imported assets or configuration, or by earlier site changes.
- Inspect src/pages for export const prerender directives. Any route that relies on Astro middleware for this response header must be server-rendered, not prerendered.
- Add or update src/middleware.ts so server-rendered responses receive the CSP header.
- Preserve any unrelated security headers and existing middleware behavior.
- Run the project's required validation, save the code, publish it, and verify the exact production custom-domain URL.
- Confirm the production response contains the expected Content-Security-Policy header and does not contain X-Frame-Options: SAMEORIGIN or DENY.
Before changing code, tell me the exact trusted parent origin and which routes will be embeddable.An origin must include its scheme. For example, http://example.com and https://example.com are different origins. If more than one parent is trusted, list each origin explicitly in the frame-ancestors directive.
1. Confirm the framing policy
- Identify the exact parent origin or origins that may embed the site.
- Use full origins such as
https://example.com. - Do not paste Markdown links, brackets, paths, or prose into the policy.
- Do not use
ALLOW-FROMwithX-Frame-Options; modern browsers do not support it.
Use Content-Security-Policy: frame-ancestors … as the authoritative policy. Unlike frame-src, which controls frames loaded by your page, frame-ancestors controls which parents may embed your page.
2. Check the Astro rendering mode
Ask Ploy to inspect every route that needs the header. This search finds route-level prerender directives:
grep -R "export const prerender" src/pagesAstro middleware runs at build time for prerendered pages and at request time for on-demand pages. A prerendered route is served as a static asset in production, so request-time middleware cannot reliably determine its final response headers.
For routes that must receive the header from middleware, remove export const prerender = true;. Apply that decision to every route that needs the policy, not only the homepage. Keep a route prerendered only when the deployment platform applies equivalent headers to its static response.
3. Add global Astro middleware
Create or update src/middleware.ts. If the project already has middleware, Ploy should preserve its existing behavior and merge this response-header logic into it.
import { defineMiddleware } from 'astro:middleware';
const CONTENT_SECURITY_POLICY =
'frame-ancestors https://trusted-parent.example;';
export const onRequest = defineMiddleware(async (_context, next) => {
const response = await next();
const headers = new Headers(response.headers);
// X-Frame-Options cannot allow an arbitrary cross-origin parent.
headers.delete('X-Frame-Options');
headers.set('Content-Security-Policy', CONTENT_SECURITY_POLICY);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
});The middleware clones the response headers, removes the incompatible legacy header, and returns a new response with the approved CSP. It applies to server-rendered pages and endpoints that pass through Astro middleware.
4. Avoid conflicting header values
DENYblocks all framing.SAMEORIGINallows only same-origin framing.ALLOW-FROMis obsolete and ignored by modern browsers.- A
<meta http-equiv="Content-Security-Policy">tag cannot deliverframe-ancestors; use the HTTP response header.
Ploy-created Astro sites do not include X-Frame-Options by default. A conflicting value may still be present if you imported external assets, middleware, or configuration, or previously asked Ploy's Agent to add framing protections. If it exists, remove the conflicting header rather than trying to make it express the same cross-origin allowlist.
5. Validate, publish, and verify production
- Run the project's required render, type, or build validation.
- Confirm every changed route still renders.
- Confirm routes relying on request-time middleware are not still prerendered.
- Save the code, then publish the target environment.
- Verify the exact custom-domain URL from an independent shell with cache bypass headers.
curl -sI \
-H 'Cache-Control: no-cache' \
-H 'Pragma: no-cache' \
https://www.example-site.com/The live response should contain Content-Security-Policy: frame-ancestors https://trusted-parent.example; and should not contain a conflicting X-Frame-Options: SAMEORIGIN or X-Frame-Options: DENY header.
Troubleshooting a missing production header
- Confirm the production deployment is newer than the code change.
- Confirm the tested URL routes to the intended Ploy site or Worker.
- Check whether the route is still prerendered and served as a static asset.
- Check whether a proxy, CDN, Worker, or platform security rule rewrites response headers.
- If the edge preserves upstream headers, make the affected route server-rendered, republish, and test again.
Completion criteria
- The approved parent origin is explicit and includes its scheme.
- Routes are server-rendered wherever request-time middleware must set the header.
- Astro middleware sets
Content-Security-Policy: frame-ancestors …. - Any conflicting
X-Frame-Optionsintroduced by imported code, configuration, or earlier site changes is removed. - Validation passes and the code is saved.
- The published custom-domain response contains the expected CSP and no incompatible framing header.
Sources
Learn more about CSP behavior and platform-specific configuration from these upstream sources.
