The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Check the transport contextif (!isSecureContext) throw new Error('Device access requires HTTPS')View examples
Read geolocation permission stateconst status = await navigator.permissions.query({ name: 'geolocation' })View examples
Observe a permission changestatus.addEventListener('change', () => updateControls(status.state))View examples
Restrict location to this originPermissions-Policy: geolocation=(self)View examples
Disable camera and microphonePermissions-Policy: camera=(), microphone=()View examples
Delegate location to one frame<iframe src="https://maps.example/" allow="geolocation" title="Store map"> </iframe>View examples
Request one positionnavigator.geolocation.getCurrentPosition(onPosition, onError, { timeout: 10000, maximumAge: 60000 })View examples
Watch position changesconst watchId = navigator.geolocation.watchPosition(onPosition, onError, options)View examples
Stop watching locationnavigator.geolocation.clearWatch(watchId)View examples
Request camera and microphoneconst stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })View examples
Stop every media trackstream.getTracks().forEach(track => track.stop())View examples
List exposed media devicesconst devices = await navigator.mediaDevices.enumerateDevices()View examples
Request a display surfaceconst stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false })View examples
Detect stopped screen sharingstream.getVideoTracks()[0].addEventListener('ended', handleShareEnded)View examples
Request orientation accessconst state = await DeviceOrientationEvent.requestPermission()View examples
Listen for orientation changesaddEventListener('deviceorientation', handleOrientation)View examples
Stop orientation updatesremoveEventListener('deviceorientation', handleOrientation)View examples
React to page visibilitydocument.addEventListener('visibilitychange', () => { if (document.hidden) stopSensitiveAccess() })View examples

Location, cameras, microphones, screens, and motion sensors expose intimate data. Ask only after the user chooses a feature whose benefit is clear, request the minimum capability, and make refusal a complete product state rather than an exception page. A browser permission, a secure context, and Permissions Policy are independent gates; satisfying one does not satisfy the others. Handle revocation and hardware failure at use time, stop sensors promptly, and never infer that a permission-state query guarantees the next operation will succeed.

Step by step

Detailed examples

01

Treat support, secure context, policy, consent, and availability as separate gates

Most powerful device APIs are restricted to secure contexts, but HTTPS alone never grants access. Permissions Policy can disable or delegate a feature, the user agent may require express permission, the operating system may deny hardware access, and a device can disappear or already be busy. Permissions API query reads the current state for supported descriptors without prompting; descriptor support varies and query can reject. granted means a prompt is not presently required, not that the operation will succeed. prompt means the feature's own request may ask, not that calling query should ask. Permission grants can expire or be revoked, so handle the actual API result and observe changes only as a UI optimization.

Enhance a location control without pre-prompting
async function prepareLocationButton(button) {
  if (!isSecureContext || !navigator.geolocation) {
    button.hidden = true;
    return;
  }

  try {
    const status = await navigator.permissions.query({ name: 'geolocation' });
    const render = () => button.dataset.permission = status.state;
    status.addEventListener('change', render);
    render();
  } catch {
    button.dataset.permission = 'unknown';
  }
}
Back to quick reference ↑
02

Set the top-level Permissions Policy before delegating to frames

The Permissions-Policy response header defines an upper bound for the response and descendant frames. An empty allowlist disables a feature; self permits the response's own origin; explicit serialized origins selectively widen access. Cross-origin frames generally do not receive powerful features by default, and an iframe allow attribute delegates only within the header's upper bound—it cannot override a header that already disabled the feature. Camera, microphone, geolocation, display-capture, accelerometer, gyroscope, and magnetometer use distinct policy names. Grant the smallest set to a trusted origin, give the frame a meaningful title, sandbox it where compatible, and remember that delegation merely allows that frame to ask the user.

Delegate only geolocation to one map origin
Permissions-Policy: geolocation=(self "https://maps.example"), camera=(), microphone=(), display-capture=(), accelerometer=(), gyroscope=(), magnetometer=()

<iframe src="https://maps.example/store-locator" allow="geolocation" title="Store locator map"></iframe>
Back to quick reference ↑
03

Request the least precise location that solves the user's task

Geolocation is secure-context-only, permission-gated, and policy-controlled. In the current specification, acquisition waits until the document is visible. getCurrentPosition obtains one reading; watchPosition continues until clearWatch and can increase battery and privacy cost. Set timeout and maximumAge from the product's real freshness needs, and enableHighAccuracy only when coarse results are insufficient because it may consume more power or take longer. accuracy is an estimate in metres, not certainty, and the API does not reveal how a position was derived. Keep manual address, region, or map selection available, minimize retention, explain downstream sharing, and never log raw coordinates by default.

Wrap a one-shot request with bounded options and clear errors
function locateStore() {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(
      position => resolve({
        latitude: position.coords.latitude,
        longitude: position.coords.longitude,
        accuracyMetres: position.coords.accuracy
      }),
      error => reject(new Error(`Location unavailable (${error.code})`)),
      { enableHighAccuracy: false, timeout: 10000, maximumAge: 60000 }
    );
  });
}
Always release a live location watch
const options = { enableHighAccuracy: false, timeout: 15000, maximumAge: 30000 };
const watchId = navigator.geolocation.watchPosition(renderPosition, renderError, options);

function stopLocationWatch() {
  navigator.geolocation.clearWatch(watchId);
}
addEventListener('pagehide', stopLocationWatch, { once: true });
Back to quick reference ↑
04

Acquire only the media tracks needed and make capture visible

getUserMedia is exposed through navigator.mediaDevices in secure contexts and is gated separately for camera and microphone by permission and Permissions Policy. Request only the kinds and constraints the user selected; overly exact constraints can fail before a useful choice exists and may reveal capability information. A granted permission does not guarantee success because devices can be absent, unavailable, or fail constraints. enumerateDevices is also privacy-gated: the list is filtered by policy, and labels or stable identifiers may remain unavailable until capture is active or permission exists. Attach streams to intended elements, provide a persistent stop control and status indicator, and stop every track on cancellation, navigation, or teardown.

Preview a camera and guarantee teardown
let cameraStream;

async function startCamera(video) {
  cameraStream = await navigator.mediaDevices.getUserMedia({
    video: { width: { ideal: 1280 }, facingMode: 'user' },
    audio: false
  });
  video.srcObject = cameraStream;
  await video.play();
}

function stopCamera(video) {
  cameraStream?.getTracks().forEach(track => track.stop());
  cameraStream = undefined;
  video.srcObject = null;
}
Back to quick reference ↑
05

Start every display share from a direct user action

getDisplayMedia requires a secure context, transient user activation, express selection in the browser's chooser, and permission from the display-capture policy-controlled feature. The browser must let the user choose a display surface each time; an application cannot persist a granted state or use constraints to silently narrow the chooser before selection. Call it directly from a clearly labeled click, describe whether audio is requested, and expect InvalidStateError or NotAllowedError when activation, policy, or consent requirements are unmet. Listen for track ended because users can stop sharing through browser chrome. Avoid the hall-of-mirrors effect, warn before sharing an entire monitor, and never treat a captured screen as proof of identity or authorization.

Share a user-selected surface and handle external stop
document.querySelector('#share-screen').addEventListener('click', async () => {
  const status = document.querySelector('#share-status');
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
    const [track] = stream.getVideoTracks();
    track.addEventListener('ended', () => { status.textContent = 'Screen sharing stopped.'; });
    document.querySelector('#screen-preview').srcObject = stream;
    status.textContent = 'Screen sharing is active.';
  } catch (error) {
    status.textContent = error.name === 'NotAllowedError' ? 'Screen sharing was not allowed.' : 'Screen sharing is unavailable.';
  }
});
Back to quick reference ↑
06

Feature-detect motion permission and provide equivalent controls

Device orientation and motion expose sensor-derived data only in secure contexts and are controlled through accelerometer, gyroscope, and sometimes magnetometer permissions and policy features. The current specification defines static requestPermission methods, but deployed behavior varies, so test for the method and call it directly from user activation where present. Register listeners only after access is granted, remove them promptly, and expect null or reduced-precision values. Coordinate meanings differ from CSS transforms, and events are not a dependable compass without an appropriate absolute reference. Always provide keyboard, pointer, and explicit UI alternatives; motion-only interaction excludes users and accidental motion must be reversible.

Enable orientation progressively from a button
async function enableOrientation() {
  if (!isSecureContext || !('DeviceOrientationEvent' in window)) return false;

  if (typeof DeviceOrientationEvent.requestPermission === 'function') {
    const state = await DeviceOrientationEvent.requestPermission();
    if (state !== 'granted') return false;
  }

  addEventListener('deviceorientation', handleOrientation);
  return true;
}

function handleOrientation(event) {
  if (event.beta == null || event.gamma == null) return;
  renderTilt({ frontBack: event.beta, leftRight: event.gamma });
}

Note: Invoke enableOrientation directly from the user's Enable motion button; do not call it on load.

Back to quick reference ↑
07

Design denial, revocation, interruption, and cleanup as normal states

Users may dismiss a prompt, revoke a previous grant, end display capture, cover a camera, disconnect hardware, background the page, or let a temporary grant expire. Map DOMException names to useful product states without exposing raw diagnostics as blame, and never loop prompts after denial. Keep manual input and upload fallbacks available where possible. Stop media tracks, geolocation watches, and sensor listeners when the task, component, or page lifecycle ends; clear rendered frames and discard high-sensitivity data according to a documented retention policy. Visible indicators inside the application complement, but never replace, browser and operating-system capture indicators.

Centralize best-effort teardown
const active = { media: undefined, watchId: undefined, orientation: false };

function stopSensitiveAccess() {
  active.media?.getTracks().forEach(track => track.stop());
  active.media = undefined;
  if (active.watchId !== undefined) navigator.geolocation.clearWatch(active.watchId);
  active.watchId = undefined;
  if (active.orientation) removeEventListener('deviceorientation', handleOrientation);
  active.orientation = false;
}

document.addEventListener('visibilitychange', () => {
  if (document.hidden) stopSensitiveAccess();
});
addEventListener('pagehide', stopSensitiveAccess);
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. World Wide Web ConsortiumPermissionsw3.org
  2. World Wide Web ConsortiumPermissions Policyw3.org
  3. World Wide Web ConsortiumGeolocationw3.org
  4. World Wide Web ConsortiumMedia Capture and Streamsw3.org
  5. World Wide Web ConsortiumScreen Capturew3.org
  6. World Wide Web ConsortiumDevice Orientation and Motionw3.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback