Skip to main content
Back to blog
April 28, 2026·Updated May 2, 2026|10 min read|Antoine Duno|Web Security

Permissions-Policy Header: Lock Down Browser APIs on Your Website

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.

Antoine Duno

1,370 words

AD

Antoine Duno

Founder of ZeriFlow · 10 years fullstack engineering · About the author

Key Takeaways

  • 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.
  • Includes copy-paste code examples and step-by-step instructions.
  • Free automated scan available to verify your implementation.

Permissions-Policy Header: Lock Down Browser APIs on Your Website

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.

From Feature-Policy to Permissions-Policy

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 Full List of Controllable APIs

The specification covers 30+ features, with browser support varying. Here are all notable ones as of 2026:

Sensor and Device APIs

FeatureWhat It Controls
cameraAccess to video input devices
microphoneAccess to audio input devices
geolocationAccess to navigator.geolocation
accelerometerDeviceMotionEvent acceleration data
gyroscopeDeviceMotionEvent rotation data
magnetometerOrientation relative to Earth''s magnetic field
ambient-light-sensorAmbientLightSensor API

Payment and Authentication

FeatureWhat It Controls
paymentPayment Request API
publickey-credentials-getWebAuthn navigator.credentials.get()

Display and Fullscreen

FeatureWhat It Controls
fullscreenElement.requestFullscreen()
display-captureScreen capture via getDisplayMedia()
picture-in-picturePicture-in-Picture API

Hardware Access

FeatureWhat It Controls
usbWebUSB API
bluetoothWeb Bluetooth API
serialWeb Serial API
hidWebHID API for human interface devices
midiWeb MIDI API
gamepadGamepad API

Privacy and Tracking

FeatureWhat It Controls
interest-cohortGoogle FLoC/Topics API (behavioral tracking)
attribution-reportingPrivacy Sandbox Attribution Reporting
browsing-topicsTopics API for interest-based advertising
federated-credentialsFederated Credential Management (FedCM)
identity-credentials-getIdentity credential access

Content and Performance

FeatureWhat It Controls
autoplayAutomatic media playback
encrypted-mediaEncrypted Media Extensions (DRM)
xr-spatial-trackingWebXR augmented/virtual reality tracking
document-domainSetting document.domain (isolation breaking)
sync-xhrSynchronous XMLHttpRequest (deprecated but still used)
shared-array-bufferSharedArrayBuffer (Spectre mitigation context)
cross-origin-isolatedHigh-resolution timer precision
focus-without-user-activationFocusing elements without user gesture

Syntax Reference

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 partner
  • No value needed for "allow all" — just omit the feature from the policy (it defaults to allowed)

Building Your Permissions-Policy

Start 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=()

Why Restricting Unused APIs Matters

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.

html
<!-- 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:

html
<!-- 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.

The interest-cohort Directive

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.

Implementation by Platform

Nginx

nginx
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)" always;

Apache

apache
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)"

Express.js with Helmet

javascript
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.js (next.config.js)

javascript
module.exports = {
  async headers() {
    return [
      {
        source: ''/(.*)'',
        headers: [
          {
            key: ''Permissions-Policy'',
            value: ''camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(self), fullscreen=(self)'',
          },
        ],
      },
    ];
  },
};

Browser Support and Fallback Behavior

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)

Verification

bash
# 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 allowed

ZeriFlow 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 articles

Keep reading