|$ curl https://forge-ai.dev/api/markdown?path=docs/js/permissions-api
$cat docs/permissions-api.md
updated Last week·18 min read·published

Permissions API

JavaScriptWeb APISecurityIntermediate🎯Free Tools
Introduction

The Permissions API provides a unified way to check the status of browser permissions for features like camera, microphone, geolocation, notifications, and more. It allows you to query whether a permission is "granted", "denied", or "prompt" (not yet decided) before attempting to use the corresponding API.

Instead of blindly calling an API and catching permission errors, you can check the permission status first and provide a better user experience. The API also fires change events when the user grants or revokes permissions, so your app can react in real time.

permissions-intro.js
JavaScript
1// Check notification permission
2const result = await navigator.permissions.query({ name: "notifications" });
3console.log(result.state); // "granted", "denied", or "prompt"
4
5// React to permission changes
6result.addEventListener("change", () => {
7 console.log("Permission changed to:", result.state);
8});
9
10// Check geolocation permission
11const geo = await navigator.permissions.query({ name: "geolocation" });
12console.log("Geolocation:", geo.state);
navigator.permissions.query()

The query() method takes a PermissionDescriptor object with a name property specifying the permission to check. It returns a Promise that resolves to a PermissionStatus object with a state property.

Permission NameAPIDescription
geolocationGeolocation APILocation access
notificationsNotifications APISystem notifications
cameraMediaDevices APICamera access
microphoneMediaDevices APIMicrophone access
clipboard-readClipboard APIRead clipboard
clipboard-writeClipboard APIWrite clipboard
midiWeb MIDI APIMIDI device access
permissions-query.js
JavaScript
1// Query a specific permission
2async function checkPermission(name) {
3 try {
4 const status = await navigator.permissions.query({ name });
5 return status.state;
6 } catch (err) {
7 // Permission name not supported in this browser
8 console.warn(`Permission "${name}" not supported:`, err.message);
9 return "unsupported";
10 }
11}
12
13// Check multiple permissions
14async function checkAllPermissions() {
15 const permissions = [
16 "geolocation",
17 "notifications",
18 "camera",
19 "microphone",
20 "clipboard-read"
21 ];
22
23 const results = {};
24 for (const name of permissions) {
25 results[name] = await checkPermission(name);
26 }
27
28 return results;
29}
30
31const status = await checkAllPermissions();
32console.log(status);
33// {
34// geolocation: "granted",
35// notifications: "prompt",
36// camera: "denied",
37// microphone: "prompt",
38// clipboard-read: "unsupported"
39// }
PermissionStatus Object

The PermissionStatus object returned by query() has a state property and a changeevent. The state can be "granted" (permission given), "denied" (permission refused), or "prompt" (user hasn't decided yet).

permissions-status.js
JavaScript
1// PermissionStatus object
2const status = await navigator.permissions.query({ name: "notifications" });
3
4console.log(status.state); // "granted", "denied", or "prompt"
5console.log(status.name); // "notifications"
6
7// Listen for permission changes
8status.addEventListener("change", () => {
9 console.log(`Permission "${status.name}" changed to: ${status.state}`);
10
11 switch (status.state) {
12 case "granted":
13 showUI("Notifications enabled");
14 break;
15 case "denied":
16 showUI("Notifications blocked — enable in settings");
17 break;
18 case "prompt":
19 showUI("Notifications not yet decided");
20 break;
21 }
22});
23
24// Practical: adaptive UI based on permissions
25async function setupFeatureUI() {
26 const geoStatus = await navigator.permissions.query({ name: "geolocation" });
27 const notifStatus = await navigator.permissions.query({ name: "notifications" });
28
29 if (geoStatus.state === "granted") {
30 showLocationMap();
31 } else if (geoStatus.state === "prompt") {
32 showLocationPrompt();
33 } else {
34 showLocationDenied();
35 }
36
37 if (notifStatus.state === "granted") {
38 enableNotifications();
39 }
40}
Camera & Microphone

Checking camera and microphone permissions before calling getUserMedia()prevents the browser from showing a permission dialog when the user doesn't expect it. This creates a smoother user experience for video/audio features.

permissions-camera.js
JavaScript
1// Check camera/mic permissions before requesting media
2async function startCameraSafely(videoElement) {
3 // Check permissions first
4 const cameraStatus = await navigator.permissions.query({ name: "camera" });
5 const micStatus = await navigator.permissions.query({ name: "microphone" });
6
7 console.log("Camera:", cameraStatus.state);
8 console.log("Microphone:", micStatus.state);
9
10 // If already denied, don't bother asking
11 if (cameraStatus.state === "denied") {
12 showSetupInstructions("camera");
13 return null;
14 }
15
16 // If granted or prompt, proceed
17 try {
18 const stream = await navigator.mediaDevices.getUserMedia({
19 video: cameraStatus.state !== "denied",
20 audio: micStatus.state !== "denied"
21 });
22
23 videoElement.srcObject = stream;
24 return stream;
25 } catch (err) {
26 console.error("Media access failed:", err);
27 if (err.name === "NotAllowedError") {
28 showSetupInstructions("camera/microphone");
29 }
30 return null;
31 }
32}
33
34function showSetupInstructions(device) {
35 const msg = `${device} access was denied. Please enable it in your browser settings.`;
36 alert(msg);
37}
38
39// Permission-aware video call setup
40async function setupVideoCall() {
41 const video = document.getElementById("local-video");
42 const camera = await navigator.permissions.query({ name: "camera" });
43 const mic = await navigator.permissions.query({ name: "microphone" });
44
45 // Update UI based on current permissions
46 updateMediaUI({
47 camera: camera.state,
48 microphone: mic.state
49 });
50
51 // Listen for changes
52 camera.addEventListener("change", () => updateMediaUI({ camera: camera.state }));
53 mic.addEventListener("change", () => updateMediaUI({ microphone: mic.state }));
54
55 // Start camera if possible
56 if (camera.state === "granted") {
57 await startCameraSafely(video);
58 }
59}
Geolocation

Geolocation permission can be checked before calling navigator.geolocation.getCurrentPosition(). This avoids showing the permission dialog when the user has already denied location access.

permissions-geolocation.js
JavaScript
1// Geolocation with permission check
2async function getLocation() {
3 try {
4 const status = await navigator.permissions.query({ name: "geolocation" });
5
6 if (status.state === "denied") {
7 console.log("Location access denied");
8 return null;
9 }
10
11 return new Promise((resolve, reject) => {
12 navigator.geolocation.getCurrentPosition(
13 (position) => {
14 resolve({
15 lat: position.coords.latitude,
16 lng: position.coords.longitude,
17 accuracy: position.coords.accuracy
18 });
19 },
20 (error) => {
21 console.error("Geolocation error:", error.message);
22 reject(error);
23 },
24 {
25 enableHighAccuracy: true,
26 timeout: 10000,
27 maximumAge: 60000
28 }
29 );
30 });
31 } catch (err) {
32 console.error("Permission check failed:", err);
33 return null;
34 }
35}
36
37// React to geolocation permission changes
38async function watchGeoPermission() {
39 const status = await navigator.permissions.query({ name: "geolocation" });
40
41 status.addEventListener("change", () => {
42 if (status.state === "granted") {
43 console.log("Location access granted — starting location tracking");
44 startLocationTracking();
45 } else if (status.state === "denied") {
46 console.log("Location access denied — stopping tracking");
47 stopLocationTracking();
48 }
49 });
50}
Progressive Enhancement

The Permissions API is not supported in all browsers (notably absent in Safari at the time of writing). Always feature-detect and provide fallbacks. The pattern of catching errors from the underlying API works everywhere, even when the Permissions API is unavailable.

permissions-progressive.js
JavaScript
1// Progressive enhancement pattern
2async function checkPermissionGracefully(permissionName) {
3 // Try Permissions API first
4 if ("permissions" in navigator) {
5 try {
6 const status = await navigator.permissions.query({ name: permissionName });
7 return {
8 supported: true,
9 state: status.state,
10 onChange: (callback) => status.addEventListener("change", callback)
11 };
12 } catch (err) {
13 // Permission name not recognized
14 }
15 }
16
17 // Fallback: return "unknown" — caller should try the API directly
18 return {
19 supported: false,
20 state: "unknown",
21 onChange: () => {} // no-op
22 };
23}
24
25// Usage
26const notifPerm = await checkPermissionGracefully("notifications");
27if (notifPerm.supported) {
28 if (notifPerm.state === "granted") {
29 enableNotifications();
30 }
31 notifPerm.onChange(() => {
32 // Re-check and update UI
33 checkPermissionGracefully("notifications").then(p => {
34 if (p.state === "granted") enableNotifications();
35 else disableNotifications();
36 });
37 });
38} else {
39 // Permissions API not available — try requesting directly
40 try {
41 const result = await Notification.requestPermission();
42 if (result === "granted") enableNotifications();
43 } catch (err) {
44 console.log("Cannot check notification permission");
45 }
46}

best practice

Use the Permissions API for progressive enhancement — check permission status before requesting access. Always provide fallbacks for browsers that don't support the Permissions API. The most reliable pattern is: check permission, then call the API, then handle errors gracefully.
Best Practices
Check permissions before requesting access — avoid unexpected permission dialogs
Listen for permission changes — update UI when user grants/revokes in browser settings
Feature-detect the Permissions API — not all browsers support all permission names
Handle 'unsupported' gracefully — some permission names may not be recognized
Never assume permission is granted — always check before accessing sensitive APIs
Use the permission state to show appropriate UI — different states need different responses
Provide clear instructions when permission is denied — help users re-enable in settings
Combine Permissions API with error handling — belt-and-suspenders approach
$Blueprint — Engineering Documentation·Section ID: JS-PERM·Revision: 1.0