TL;DR: Permissions-Policy is the current header. Feature-Policy is the deprecated older form. The feature names are familiar, but the syntax changed enough that copy-paste migrations often break silently.
The Fastest Way To Think About This
If you are migrating old security headers, the important point is not “which features do these headers control?” It is:
Feature-Policyis the old headerPermissions-Policyis the modern header- the syntax changed enough that a naive rename is wrong
The Core Difference
Both headers control browser capabilities like geolocation, microphone, and camera for a page and its embedded contexts. Feature-Policy is the deprecated original. Permissions-Policy is the current replacement with structured syntax.
Permissions-Policy uses structured field values with parenthesized allowlists:
Permissions-Policy: geolocation=(self), microphone=(), camera=(self "https://video.example.com")
Feature-Policy used semicolon-separated directives with quoted keywords:
Feature-Policy: geolocation 'self'; microphone 'none'; camera 'self' https://video.example.com
The feature names (geolocation, microphone, camera, etc.) are identical — only the syntax around them changed.
Syntax Comparison
| Aspect | Permissions-Policy | Feature-Policy |
|---|---|---|
| Directive separator | , (comma) | ; (semicolon) |
| Block all origins | feature=() | feature 'none' |
| Allow all origins | feature=* | feature * |
| Same origin only | feature=(self) | feature 'self' |
| Specific origin | feature=("https://example.com") | feature https://example.com |
| Multiple origins | feature=(self "https://a.com") | feature 'self' https://a.com |
| Assignment syntax | feature=(allowlist) | feature allowlist |
The biggest change is the move from quoted keywords ('self', 'none') to structured values ((self), ()).
Geolocation
The most commonly searched feature. Here’s how the syntax maps:
Block Geolocation Entirely
# Permissions-Policy (current)
Permissions-Policy: geolocation=()
# Feature-Policy (deprecated)
Feature-Policy: geolocation 'none'
Allow Geolocation for Same Origin
# Permissions-Policy (current)
Permissions-Policy: geolocation=(self)
# Feature-Policy (deprecated)
Feature-Policy: geolocation 'self'
Allow Geolocation for Specific Origins
# Permissions-Policy (current)
Permissions-Policy: geolocation=(self "https://maps.googleapis.com")
# Feature-Policy (deprecated)
Feature-Policy: geolocation 'self' https://maps.googleapis.com
Microphone
Block Microphone Access
# Permissions-Policy (current)
Permissions-Policy: microphone=()
# Feature-Policy (deprecated)
Feature-Policy: microphone 'none'
Allow Microphone for Video Chat
# Permissions-Policy (current)
Permissions-Policy: microphone=(self "https://meet.example.com")
# Feature-Policy (deprecated)
Feature-Policy: microphone 'self' https://meet.example.com
Camera
Block Camera Access
# Permissions-Policy (current)
Permissions-Policy: camera=()
# Feature-Policy (deprecated)
Feature-Policy: camera 'none'
Allow Camera for Same Origin
# Permissions-Policy (current)
Permissions-Policy: camera=(self)
# Feature-Policy (deprecated)
Feature-Policy: camera 'self'
Multiple Features
Real-world policies typically combine multiple feature directives. Here’s a full migration example:
Feature-Policy (deprecated)
Feature-Policy: geolocation 'self'; microphone 'none'; camera 'none'; payment 'self' https://checkout.stripe.com; autoplay 'self'
Permissions-Policy (current)
Permissions-Policy: geolocation=(self), microphone=(), camera=(), payment=(self "https://checkout.stripe.com"), autoplay=(self)
Migration Checklist
- Replace the header name —
Feature-Policy→Permissions-Policy - Change separators —
;→, - Add
=()assignment —feature allowlist→feature=(allowlist) - Replace
'none'—'none'→() - Replace
'self'—'self'→self(no quotes, inside parentheses) - Quote specific origins —
https://example.com→"https://example.com"(inside parentheses) - Test in DevTools — verify features are correctly allowed or blocked
- Remove Feature-Policy — once you no longer need legacy browser support
Server Configuration
Nginx Migration
# Before (deprecated)
add_header Feature-Policy "geolocation 'self'; microphone 'none'; camera 'none'";
# After (current)
add_header Permissions-Policy "geolocation=(self), microphone=(), camera=()";
Apache Migration
# Before (deprecated)
Header always set Feature-Policy "geolocation 'self'; microphone 'none'; camera 'none'"
# After (current)
Header always set Permissions-Policy "geolocation=(self), microphone=(), camera=()"
Express.js Migration
// Before (deprecated)
app.use((req, res, next) => {
res.set(
"Feature-Policy",
"geolocation 'self'; microphone 'none'; camera 'none'",
);
next();
});
// After (current)
app.use((req, res, next) => {
res.set("Permissions-Policy", "geolocation=(self), microphone=(), camera=()");
next();
});
Browser Support
| Browser | Permissions-Policy | Feature-Policy |
|---|---|---|
| Chrome | 88+ | 60+ (legacy, still recognized) |
| Edge | 88+ | 79+ (legacy) |
| Firefox | Partial (subset of features) | See MDN compatibility table |
| Safari | 15.4+ (partial) | See MDN compatibility table |
| Opera | 74+ | 47+ (legacy) |
Feature-Policy is deprecated and no longer developed, and its syntax differs, so a site that sends only the old header will not get any of the newer Permissions-Policy features (for example origin allowlists in the structured syntax). Send Permissions-Policy.
Common Mistakes
Mixing old and new syntax — using Permissions-Policy: geolocation 'self' (Feature-Policy syntax with the Permissions-Policy header name) is silently ignored by browsers. The value must use the new structured field syntax.
Forgetting to quote origins — in Permissions-Policy, specific origins must be quoted: geolocation=(self "https://example.com"). Unquoted origins are silently ignored.
Using semicolons as separators — Permissions-Policy: geolocation=(self); microphone=() is incorrect. Use commas: Permissions-Policy: geolocation=(self), microphone=().
Only sending Feature-Policy — if you only send Feature-Policy, modern Chromium browsers ignore it completely. Current protection requires Permissions-Policy.
FAQ
Is Feature-Policy deprecated?
Yes. Feature-Policy was renamed to Permissions-Policy. Chromium shipped Feature-Policy first (Chrome 60) and has kept recognizing the old header alongside Permissions-Policy, but the spec is deprecated and new code should send Permissions-Policy only. Check MDN’s compatibility table before depending on Feature-Policy in any browser.
What changed between Feature-Policy and Permissions-Policy?
The syntax changed significantly. Semicolons became commas as separators. Quoted keywords like ‘self’ and ‘none’ became unquoted self and empty parentheses (). Space-separated allowlists are now wrapped in parentheses. Feature names stayed the same.
How do I migrate geolocation from Feature-Policy to Permissions-Policy?
Change Feature-Policy: geolocation ‘self’ to Permissions-Policy: geolocation=(self). To block entirely, change geolocation ‘none’ to geolocation=(). To allow specific origins, change geolocation ‘self’ https://maps.example.com to geolocation=(self “https://maps.example.com”).
How do I migrate microphone and camera from Feature-Policy to Permissions-Policy?
Change Feature-Policy: microphone ‘none’; camera ‘none’ to Permissions-Policy: microphone=(), camera=(). The pattern is the same for all features — replace ‘none’ with (), ‘self’ with (self), and wrap origin lists in parentheses.
Can I use both Feature-Policy and Permissions-Policy at the same time?
Yes, but it is not recommended. If both headers are present, Permissions-Policy takes precedence in browsers that support it. Older browsers that only understand Feature-Policy will use that header. During migration, you can send both temporarily, but remove Feature-Policy once you no longer need to support very old browsers.
Do all browsers support Permissions-Policy?
Chrome and Edge 88+ and Opera 74+ support the Permissions-Policy header. Firefox and Safari support it with a smaller set of recognized features, and unknown feature names are ignored. Support differs per feature, so check the MDN compatibility table for the specific directives you set.