Permissions-Policy
Browsers ship a long list of powerful APIs: camera, microphone, geolocation, payment sheets, USB, Bluetooth, clipboard access, WebAuthn. Most sites need a handful. A marketing page or a docs site needs none. Every feature you leave reachable is one more thing an injected script can try to trigger.
Permissions-Policy is the response header that lets you turn those features off for the page (and for any iframes inside it). Set once, enforced by the browser on every API call.
Why restrict features
Two reasons.
- Defence in depth. If an attacker manages to inject a script despite CSP, a policy that disables
cameraandgeolocationmeans the attacker cannot prompt the user even if the rest of the app could. Fewer attack surfaces per compromise. - Vendor control. Third-party iframes (ads, chat widgets, embedded media) inherit permissions from your page by default in older browsers. Explicit policy makes the allowlist exact instead of "whatever the browser happened to do."
Syntax
The header uses HTTP Structured Fields. Each directive is name=allowlist, directives separated by commas.
Permissions-Policy: camera=(), geolocation=(self), payment=(self "https://checkout.vendor.com")Allowlist tokens:
()empty: the feature is disabled everywhere on the page and in every iframe.*wildcard: allowed in all contexts, including cross-origin iframes.self: allowed in same-origin contexts only."https://origin": allowed in the named origin's iframes.
A useful defensive default for a site that uses none of these APIs:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=(), hid=(), serial=(), clipboard-read=(), display-capture=()The registry
The feature list is a living document maintained by the W3C working group, not baked into the spec itself. New features are added as browser APIs ship.
The spec itself is the W3C Permissions Policy Working Draft (currently October 2025). It specifies the framework and algorithm; the features are the companion document above.
Iframe delegation
The header on the parent page sets a ceiling for what any cross-origin iframe can use. The iframe tag's allow attribute is what actually delegates a feature down into that iframe. Both are required.
Permissions-Policy: camera=(self "https://widget.vendor.com")
<iframe src="https://widget.vendor.com/"
allow="camera">navigator.mediaDevices.getUserMedia(...)
Cross-origin iframes need both: the parent's Permissions-Policy must include the iframe's origin, AND the iframe tag must ship a matching allow attribute. Miss either and the feature is blocked inside the frame even if it works on the parent page.
Toggle either side and watch the API call inside the iframe change state. The pattern is the same as CSP frame-ancestors in spirit: the parent decides the boundary, the child requests what it needs, the browser intersects.
Migration from Feature-Policy
Feature-Policy was the 2018 original. It is deprecated and being removed from specs, though browsers still accept it for compatibility. The syntaxes look similar but are not interchangeable:
Feature-Policy: camera 'self' https://vendor.com
Permissions-Policy: camera=(self "https://vendor.com")Values in Permissions-Policy use the Structured Fields list syntax (parentheses, quoted strings, explicit tokens). Ship both during migration if you still serve old browsers, then drop Feature-Policy.
Common mistakes
Every feature is potentially reachable by script. Cheapest defence in depth on the web: disable them explicitly.
camera 'self' (space-separated, quoted 'self') is Feature-Policy syntax. Permissions-Policy needs camera=(self). Mixed-up values are silently ignored.
Parent's Permissions-Policy allows the vendor's origin, but the iframe tag has no allow="camera". Feature is blocked inside the frame. Ship both.
* instead of origin listcamera=* allows every iframe, including any vendor iframe that loads another iframe from its own vendor. Name origins explicitly.
API responses, /static/ assets, and framed pages all also honour the policy. Set it at the edge so every response gets it.
Check which features your site has left reachable, and whether the policy cleanly matches what you actually use: scan your domain.