The Permissions-Policy header gives you fine-grained control over which browser APIs your pages and embedded content can access. It replaced the deprecated Feature-Policy header and is now a critical privacy and security control for any modern web application.
Anay Pandya
1,370 words
Anay Pandya
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
Modern browsers expose a remarkable range of APIs — camera access, microphone input, geolocation, Bluetooth, USB, payment handlers, biometric sensors, and more. These capabilities are powerful and legitimate when used intentionally. They are dangerous when accessed by compromised third-party scripts, malicious iframes, or injected XSS payloads.
The Permissions-Policy HTTP response header (which replaced the deprecated Feature-Policy header in 2020) gives you a declarative way to control which of these APIs your page — and any content embedded within it — is allowed to access. It is part of the "defense in depth" approach: even if your CSP has a gap and a malicious script runs, a restrictive Permissions-Policy can prevent that script from silently activating the user''s microphone or extracting location data.
Keep reading
Is your site actually secure?
Run a free check — 60 seconds
If you have a Feature-Policy header on your site, it is still interpreted by some browsers for backwards compatibility, but it is deprecated. The replacement is Permissions-Policy with a different syntax.
Feature-Policy (deprecated):
Feature-Policy: camera ''none''; microphone ''none''; geolocation ''self''Permissions-Policy (current):
Permissions-Policy: camera=(), microphone=(), geolocation=(self)Key syntax differences:
- Values use structured field syntax: feature=(allowlist) instead of feature value
- ''none'' becomes an empty list ()
- ''self'' becomes (self) without quotes
- Origins are specified directly: (self "https://partner.example.com")
- Multiple features are separated by commas
If you set both headers, modern browsers use Permissions-Policy. Set only Permissions-Policy going forward.
The specification covers 30+ features, with browser support varying. Here are all notable ones as of 2026:
| Feature | What It Controls |
|---|---|
camera | Access to video input devices |
microphone | Access to audio input devices |
geolocation | Access to navigator.geolocation |
accelerometer | DeviceMotionEvent acceleration data |
gyroscope | DeviceMotionEvent rotation data |
magnetometer | Orientation relative to Earth''s magnetic field |
ambient-light-sensor | AmbientLightSensor API |
| Feature | What It Controls |
|---|---|
payment | Payment Request API |
publickey-credentials-get | WebAuthn navigator.credentials.get() |
| Feature | What It Controls |
|---|---|
fullscreen | Element.requestFullscreen() |
display-capture | Screen capture via getDisplayMedia() |
picture-in-picture | Picture-in-Picture API |
| Feature | What It Controls |
|---|---|
usb | WebUSB API |
bluetooth | Web Bluetooth API |
serial | Web Serial API |
hid | WebHID API for human interface devices |
midi | Web MIDI API |
gamepad | Gamepad API |
| Feature | What It Controls |
|---|---|
interest-cohort | Google FLoC/Topics API (behavioral tracking) |
attribution-reporting | Privacy Sandbox Attribution Reporting |
browsing-topics | Topics API for interest-based advertising |
federated-credentials | Federated Credential Management (FedCM) |
identity-credentials-get | Identity credential access |
| Feature | What It Controls |
|---|---|
autoplay | Automatic media playback |
encrypted-media | Encrypted Media Extensions (DRM) |
xr-spatial-tracking | WebXR augmented/virtual reality tracking |
document-domain | Setting document.domain (isolation breaking) |
sync-xhr | Synchronous XMLHttpRequest (deprecated but still used) |
shared-array-buffer | SharedArrayBuffer (Spectre mitigation context) |
cross-origin-isolated | High-resolution timer precision |
focus-without-user-activation | Focusing elements without user gesture |
Permissions-Policy: feature=(allowlist), feature=(allowlist), ...Allowlist values:
() — empty list, feature disabled for all origins including the page itself(self) — allowed for the same origin only, blocked for all third parties("https://specific.example.com") — allowed for specific origin only(self "https://specific.example.com") — allowed for self and a named partnerStart from a restrictive baseline and allow only what your application uses.
Step 1: Identify what your application legitimately uses. A typical SaaS app might use payment (Stripe), fullscreen (video player), and nothing else.
Step 2: Block everything you do not use:
Permissions-Policy:
accelerometer=(),
ambient-light-sensor=(),
autoplay=(),
bluetooth=(),
browsing-topics=(),
camera=(),
display-capture=(),
document-domain=(),
encrypted-media=(),
fullscreen=(self),
gamepad=(),
geolocation=(),
gyroscope=(),
hid=(),
identity-credentials-get=(),
idle-detection=(),
interest-cohort=(),
magnetometer=(),
microphone=(),
midi=(),
payment=(self),
picture-in-picture=(),
publickey-credentials-get=(self),
screen-wake-lock=(),
serial=(),
sync-xhr=(),
usb=(),
xr-spatial-tracking=()Step 3: Grant exceptions for specific third-party needs:
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(self "https://maps.googleapis.com"),
payment=(self "https://js.stripe.com"),
fullscreen=(self),
interest-cohort=()The security case for Permissions-Policy is stronger than it first appears.
XSS mitigation: If an attacker injects a script via XSS and that script tries to activate the microphone to eavesdrop on the user, microphone=() blocks the attempt even if your CSP failed to prevent the script from running in the first place.
Third-party script containment: Analytics, chat widgets, A/B testing tools, and marketing pixels often have broad API access if not restricted. A Permissions-Policy restricts these third-party scripts to the same API access as your own code.
Iframe security: When you embed third-party content in iframes (video embeds, ads, widgets), those iframes cannot use browser APIs you have restricted — even if those iframes have their own permissions from their origin. Your page''s policy creates a ceiling.
<!-- Even if the embedded content requests camera access,
the embedding page''s Permissions-Policy: camera=() blocks it -->
<iframe src="https://third-party-widget.example.com/embed"></iframe>You can also grant per-iframe permissions using the allow attribute:
<!-- Grant camera only to this specific iframe, not globally -->
<iframe
src="https://video-call.example.com/room/123"
allow="camera; microphone"
></iframe>The allow attribute on the iframe can only grant permissions that the embedding page already has — it cannot expand beyond the page-level Permissions-Policy.
This one deserves special attention. Google''s Federated Learning of Cohorts (FLoC) — and its successor, the Topics API — work by having the browser classify the user''s browsing history into interest categories and share those categories with advertisers.
This happens at the browser level, not the page level. But a page can opt out on behalf of its visitors:
Permissions-Policy: interest-cohort=()This tells Chrome not to include visits to your site in the user''s FLoC/Topics calculation. It is a meaningful privacy protection for your users — especially relevant for healthcare, legal, financial, or other sensitive platforms where you do not want visits to your site feeding behavioral advertising profiles.
GitHub deployed this header site-wide in 2021 as a statement of intent regarding user privacy.
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)" always;Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)"import helmet from ''helmet'';
app.use(helmet.permittedCrossDomainPolicies());
// Helmet does not yet have a built-in permissionsPolicy helper that covers all features,
// so set it directly:
app.use((req, res, next) => {
res.setHeader(
''Permissions-Policy'',
''camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)''
);
next();
});next.config.js)module.exports = {
async headers() {
return [
{
source: ''/(.*)'',
headers: [
{
key: ''Permissions-Policy'',
value: ''camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)'',
},
],
},
];
},
};Permissions-Policy is supported in:
- Chrome 88+ (January 2021)
- Edge 88+ (January 2021)
- Firefox 74+ (partial support, improving)
- Safari 16+ (September 2022)
For browsers that do not support Permissions-Policy, there is no fallback enforcement — the header is silently ignored. This means your policy provides no protection in those browsers, but also causes no breaking changes. Maintain Feature-Policy alongside Permissions-Policy if you need coverage for older browser versions.
Feature-Policy: camera ''none''; microphone ''none''; geolocation ''self''
Permissions-Policy: camera=(), microphone=(), geolocation=(self)# Check your header
curl -I https://yourapp.com | grep -i permissions-policy
# Test in browser DevTools
# Open DevTools → Application → Permissions Policy
# Chrome shows each feature and whether it is allowedZeriFlow checks for Permissions-Policy as part of its 80+ security header audit, flagging missing headers and recommending specific values based on your application type. Run a free scan at zeriflow.com.
Check all your security headers instantly — free.
See exactly which headers are missing and how to fix them.
Related resources
Website Vulnerability Scanner
Run a broader website security audit across headers, TLS, DNS, cookies, SEO, and disclosure checks.
Security Headers Checker
Check CSP, HSTS, X-Frame-Options, and other response headers.
SSL Checker
Review TLS certificate, HTTPS, and transport security signals.
DMARC Checker
Validate email authentication records for domain spoofing protection.
CSP Checker
Review Content-Security-Policy coverage and common gaps.
April 30, 2026
Best SAST Tools for Small Development Teams in 2026 (Free Included)
Monitor your security score
Schedule recurring scans and catch regressions after deployments.