- Home
- HTTP Headers
- Permissions-Policy Header
Header
Permissions-Policy Header
Learn how the Permissions-Policy header controls which browser features and APIs can be used in your site and embedded iframes. Enhance security and privacy.
What is Permissions-Policy?
TL;DR: Controls which browser features and APIs can be used by your site and embedded content. Blocks access to camera, geolocation, and other sensitive features for security.
The Permissions-Policy header (formerly Feature-Policy) allows you to control which browser features and APIs can be used in your site and any embedded iframes. It’s a powerful security mechanism to prevent malicious scripts or third-party content from accessing sensitive features like camera, microphone, or geolocation.
Think of it as a security guard that says “these features are allowed on this site, and these third-party embeds can only use specific features we approve.”
How Permissions-Policy Works
Restrict camera and microphone access:
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy: camera=(), microphone=()
<!DOCTYPE html>
<html>...
```text
**Allow geolocation for specific origin:**
```http
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy: geolocation=(self "https://maps.example.com")
<!DOCTYPE html>
<html>...
Syntax
Permissions-Policy: <feature>=(<allowlist>)
Permissions-Policy: <feature1>=(<allowlist1>), <feature2>=(<allowlist2>)
```text
### Allowlist Values
- **`*`** - Allow for all origins (default for most features)
- **`self`** - Allow for same origin only
- **`()`** - Block for all origins (including same origin)
- **`"https://example.com"`** - Allow for specific origin
## Common Features
### Media Capture
```http
# Block camera access
Permissions-Policy: camera=()
# Block microphone access
Permissions-Policy: microphone=()
# Allow camera for same origin only
Permissions-Policy: camera=(self)
# Allow microphone for specific domains
Permissions-Policy: microphone=(self "https://video-chat.example.com")
Geolocation
# Block geolocation
Permissions-Policy: geolocation=()
# Allow for same origin only
Permissions-Policy: geolocation=(self)
# Allow for same origin and maps provider
Permissions-Policy: geolocation=(self "https://maps.googleapis.com")
```text
### Payment
```http
# Block payment request API
Permissions-Policy: payment=()
# Allow only on same origin
Permissions-Policy: payment=(self)
# Allow for payment provider
Permissions-Policy: payment=(self "https://checkout.stripe.com")
Autoplay
# Block autoplay
Permissions-Policy: autoplay=()
# Allow autoplay on same origin
Permissions-Policy: autoplay=(self)
# Allow autoplay for specific video provider
Permissions-Policy: autoplay=(self "https://www.youtube.com")
```text
### Fullscreen
```http
# Block fullscreen
Permissions-Policy: fullscreen=()
# Allow fullscreen on same origin
Permissions-Policy: fullscreen=(self)
# Allow fullscreen for video player
Permissions-Policy: fullscreen=(self "https://player.vimeo.com")
Complete Feature List
Sensor APIs
Permissions-Policy: accelerometer=(self)
Permissions-Policy: ambient-light-sensor=()
Permissions-Policy: gyroscope=(self)
Permissions-Policy: magnetometer=()
```http
### Media APIs
```http
Permissions-Policy: camera=()
Permissions-Policy: microphone=()
Permissions-Policy: speaker-selection=()
Permissions-Policy: display-capture=()
Privacy APIs
Permissions-Policy: geolocation=(self)
Permissions-Policy: usb=()
Permissions-Policy: serial=()
Permissions-Policy: bluetooth=()
```http
### Presentation APIs
```http
Permissions-Policy: fullscreen=(self)
Permissions-Policy: screen-wake-lock=()
Permissions-Policy: picture-in-picture=(self)
Other Features
Permissions-Policy: autoplay=(self)
Permissions-Policy: encrypted-media=(self)
Permissions-Policy: payment=(self)
Permissions-Policy: interest-cohort=()
Permissions-Policy: browsing-topics=()
```text
## Real-World Scenarios
### Secure Default Policy
```http
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=(),
interest-cohort=()
Blocks all sensitive features by default.
Video Conference Application
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy:
camera=(self),
microphone=(self),
display-capture=(self),
fullscreen=(self),
autoplay=(self)
```text
Allows necessary media features for video calls.
### E-commerce Site
```http
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy:
payment=(self "https://checkout.stripe.com"),
geolocation=(self "https://maps.googleapis.com"),
camera=(),
microphone=(),
interest-cohort=()
Allows payment and maps, blocks unnecessary features.
Blog with Embedded Videos
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy:
autoplay=(self "https://www.youtube.com" "https://player.vimeo.com"),
fullscreen=(self "https://www.youtube.com" "https://player.vimeo.com"),
camera=(),
microphone=(),
geolocation=()
```text
Allows video features for embeds, blocks sensitive APIs.
### Privacy-Focused Site
```http
HTTP/1.1 200 OK
Content-Type: text/html
Permissions-Policy:
interest-cohort=(),
browsing-topics=(),
camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=(),
serial=(),
bluetooth=()
Blocks tracking and all sensitive features.
Server Configuration
Node.js (Express)
const express = require('express')
const app = express()
// Secure default policy
app.use((req, res, next) => {
res.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=(), interest-cohort=()'
)
next()
})
// Route-specific policy
app.get('/video-call', (req, res) => {
res.set('Permissions-Policy', 'camera=(self), microphone=(self), display-capture=(self)')
res.sendFile('video-call.html')
})
app.get('/checkout', (req, res) => {
res.set('Permissions-Policy', 'payment=(self "https://checkout.stripe.com")')
res.sendFile('checkout.html')
})
```javascript
### Helmet.js Middleware
```javascript
const helmet = require('helmet')
app.use(
helmet.permissionsPolicy({
features: {
camera: ["'none'"],
microphone: ["'none'"],
geolocation: ["'self'"],
payment: ["'self'", 'https://checkout.stripe.com'],
fullscreen: ["'self'"],
autoplay: ["'self'", 'https://www.youtube.com'],
'interest-cohort': ["'none'"]
}
})
)
Nginx
# Global policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()";
# Location-specific policy
location /video-call {
add_header Permissions-Policy "camera=(self), microphone=(self), display-capture=(self)";
}
location /checkout {
add_header Permissions-Policy "payment=(self \"https://checkout.stripe.com\")";
}
```javascript
### Apache
```apache
# Global policy
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
# Directory-specific policy
<Directory "/var/www/html/video-call">
Header always set Permissions-Policy "camera=(self), microphone=(self), display-capture=(self)"
</Directory>
<Directory "/var/www/html/checkout">
Header always set Permissions-Policy "payment=(self \"https://checkout.stripe.com\")"
</Directory>
Python (Flask)
from flask import Flask, Response
app = Flask(__name__)
# Default policy
@app.after_request
def set_permissions_policy(response):
response.headers['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=(), interest-cohort=()'
return response
# Route-specific policy
@app.route('/video-call')
def video_call():
response = Response(render_template('video-call.html'))
response.headers['Permissions-Policy'] = 'camera=(self), microphone=(self), display-capture=(self)'
return response
@app.route('/checkout')
def checkout():
response = Response(render_template('checkout.html'))
response.headers['Permissions-Policy'] = 'payment=(self "https://checkout.stripe.com")'
return response
```text
## HTML Equivalent
Permissions-Policy can also be set via iframe attributes:
### HTTP Header
```http
Permissions-Policy: camera=(self "https://trusted.com")
iframe allow attribute
<iframe src="https://trusted.com/widget" allow="camera 'self' https://trusted.com"></iframe>
```text
The HTTP header sets the policy for the entire page, while `allow` attribute controls individual iframes.
## Migration from Feature-Policy
Permissions-Policy replaces the deprecated Feature-Policy header:
### Old (Feature-Policy)
```http
Feature-Policy: camera 'self'; microphone 'none'; geolocation 'self' https://maps.example.com
New (Permissions-Policy)
Permissions-Policy: camera=(self), microphone=(), geolocation=(self "https://maps.example.com")
```text
**Key Differences:**
- Semicolon (`;`) → Comma (`,`)
- Space-separated → Parentheses with allowlist
- `'none'` → `()`
- `'*'` → `*`
## Best Practices
### 1. Block Unnecessary Features
```http
# ✅ Block features you don't use
Permissions-Policy: camera=(), microphone=(), usb=(), serial=()
# ❌ Allow everything by default
Permissions-Policy: camera=*, microphone=*, usb=*
2. Use Principle of Least Privilege
# ✅ Only allow what's needed
Permissions-Policy: payment=(self "https://checkout.stripe.com")
# ❌ Too permissive
Permissions-Policy: payment=*
```text
### 3. Block Tracking Features
```http
# ✅ Opt out of FLoC/Topics
Permissions-Policy: interest-cohort=(), browsing-topics=()
4. Audit Third-Party Embeds
# ✅ Explicitly list allowed origins
Permissions-Policy: autoplay=(self "https://www.youtube.com" "https://player.vimeo.com")
# ❌ Allow all origins
Permissions-Policy: autoplay=*
```javascript
### 5. Test Before Deploying
```javascript
// Test in development
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'development') {
console.log('Permissions-Policy:', res.get('Permissions-Policy'))
}
next()
})
Common Patterns
Public Website
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=(),
interest-cohort=()
```text
### Video Chat App
```http
Permissions-Policy:
camera=(self),
microphone=(self),
display-capture=(self),
fullscreen=(self)
Banking App
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(self),
payment=(self),
usb=(),
serial=(),
bluetooth=(),
interest-cohort=()
```text
### News Site with Ads
```http
Permissions-Policy:
autoplay=(self "https://ads.example.com"),
camera=(),
microphone=(),
geolocation=(),
interest-cohort=()
Testing Permissions-Policy
Browser DevTools
- Open DevTools (F12)
- Go to Console
- Try accessing restricted feature:
// Will fail if camera=()
navigator.mediaDevices.getUserMedia({ video: true })
```text
### Security Headers Checker
```bash
curl -I https://example.com | grep -i permissions-policy
Online Tools
Browser Support
Permissions-Policy is supported in:
- Chrome 88+
- Edge 88+
- Firefox 74+ (partial support)
- Safari 15.4+ (partial support)
Feature-Policy is deprecated but still works for backward compatibility.
Common Issues
Conflicting Policies
# Header
Permissions-Policy: camera=(self)
# iframe attribute overrides
<iframe src="..." allow="camera 'none'"></iframe>
```text
iframe `allow` attribute can only further restrict, not loosen.
### Incorrect Syntax
```http
# ❌ Wrong syntax (using old Feature-Policy style)
Permissions-Policy: camera 'self'; microphone 'none'
# ✅ Correct syntax
Permissions-Policy: camera=(self), microphone=()
Missing Quotes for Origins
# ❌ Missing quotes for specific origin
Permissions-Policy: camera=(self https://example.com)
# ✅ Quotes required for specific origins
Permissions-Policy: camera=(self "https://example.com")
```text
## Security Considerations
### Defense in Depth
Permissions-Policy is one layer of security:
```http
# Combine with other security headers
Permissions-Policy: camera=(), microphone=()
Content-Security-Policy: default-src 'self'
X-Frame-Options: DENY
Iframe Sandboxing
<!-- Further restrict iframes -->
<iframe
src="https://third-party.com"
sandbox="allow-scripts allow-same-origin"
allow="autoplay 'self'"></iframe>
Related Headers
- Content-Security-Policy - Script and resource restrictions
- X-Frame-Options - Clickjacking protection
- Cross-Origin-Embedder-Policy - Cross-origin isolation
Frequently Asked Questions
What is Permissions-Policy?
Permissions-Policy controls which browser features your page and embedded iframes can use. It can disable geolocation, camera, microphone, and other powerful APIs.
What happened to Feature-Policy?
Feature-Policy was renamed to Permissions-Policy. The syntax changed from Feature-Policy: geolocation none to Permissions-Policy: geolocation=().
How do I disable a feature?
Use empty parentheses to disable: Permissions-Policy: geolocation=(), camera=(). This prevents your page and all iframes from using these features.
How do I allow features for specific origins?
List origins in parentheses: Permissions-Policy: geolocation=(self "https://maps.example.com"). self allows your origin, quoted URLs allow specific others.