HTTP

Comparison

Permissions-Policy vs Feature-Policy

Understand the differences between Permissions-Policy and Feature-Policy headers. Compare syntax changes for geolocation, microphone, camera directives and learn how to migrate.

Bottom line: 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.

Permissions-Policy
vs
Feature-Policy

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:

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

AspectPermissions-PolicyFeature-Policy
Directive separator, (comma); (semicolon)
Block all originsfeature=()feature 'none'
Allow all originsfeature=*feature *
Same origin onlyfeature=(self)feature 'self'
Specific originfeature=("https://example.com")feature https://example.com
Multiple originsfeature=(self "https://a.com")feature 'self' https://a.com
Assignment syntaxfeature=(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

  1. Replace the header nameFeature-PolicyPermissions-Policy
  2. Change separators;,
  3. Add =() assignmentfeature allowlistfeature=(allowlist)
  4. Replace 'none''none'()
  5. Replace 'self''self'self (no quotes, inside parentheses)
  6. Quote specific originshttps://example.com"https://example.com" (inside parentheses)
  7. Test in DevTools — verify features are correctly allowed or blocked
  8. 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=()"
```text

### Express.js Migration

```javascript
// 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

BrowserPermissions-PolicyFeature-Policy
Chrome88+60–87
Edge88+79–87
Firefox74+ (partial)74+ (partial)
Safari15.4+ (partial)Not supported
Opera74+47–73

Chrome dropped Feature-Policy support entirely in version 88, so any site still sending only Feature-Policy headers gets no protection in modern Chrome.

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 separatorsPermissions-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.