Skip to content
Learn

Screen Wake Lock API: a practical guide with error handling

navigator.wakeLock.request('screen') asks the browser to keep the display on. It works only on a secure (HTTPS) page that is visible. The browser releases the lock when the page is hidden and never asks again by itself. This guide gives you code for each step, the reasons a request fails, and support as of 26 September 2026.

Sources checked Saturday, 26 September 20267 min readBy Soubhik Biswas

Which browsers support it?

Versions as of 26 September 2026. Support claims come from browser documentation and engine source; real-device results appear in the wake lock browser support matrix once recorded, with platforms, notes and sources for each row.

  • Chrome and Edge84 and later
  • Opera70 and later
  • Samsung Internet14 and later
  • Firefox126 and later (May 2024)
  • Safari on Mac, iPhone and iPad16.4 and later (March 2023)
  • iPhone and iPad Home Screen web appsiOS 18.4 and later

How do I ask for a wake lock?

Call request('screen') from a visible page served over HTTPS. Keep the object it returns: it is your only proof that the lock is held, and its release event tells you when it is gone. The wakeLock property only exists in secure contexts, so check with 'wakeLock' in navigator rather than calling and catching: calling request on undefined throws a TypeError that looks like a bug in your code. https:// pages and http://localhost count as secure.

wake-lock.js
let sentinel = null;let wanted = false; async function keepScreenOn() {  wanted = true;  if (!('wakeLock' in navigator)) return 'unsupported';  try {    const s = await navigator.wakeLock.request('screen');    s.addEventListener('release', () => {      if (sentinel === s) sentinel = null;    });    sentinel = s;    return 'held';  } catch (err) {    // NotAllowedError: page hidden, Permissions-Policy,    // no tap yet in Safari, or Firefox at 5% battery or less    console.warn(`${err.name}: ${err.message}`);    return 'refused';  }} startButton.addEventListener('click', keepScreenOn);

Start the request from a click handler: that click is the tap Safari needs. The wanted flag separates "the user asked for the screen to stay on" from "the browser currently holds a lock". The browser can drop the second at any time; only the user should change the first. @awaketab/wake wraps this pattern, with a gesture-aware video fallback and a change event every time the state moves. Its source is MIT on GitHub, and the npm package is coming soon; the @awaketab/wake library page runs a live demo.

Why did request() throw NotAllowedError?

The browser rejects the promise with NotAllowedError for a small set of reasons. Battery saver modes are not one of them in Chromium or Safari. Here is what each cause looks like and what AwakeTab shows.

  • The page is hidden or not activeThe request is rejected, and a held lock is released. AwakeTab shows "Paused — tab hidden" and asks again when you come back.
  • A Permissions-Policy blocks itCommon in an iframe without allow="screen-wake-lock". AwakeTab shows "Blocked — here's the fix".
  • Safari has not had a tap yetSafari wants a recent tap on the page. AwakeTab shows "Blocked — here's the fix", and tapping Retry is that tap.
  • Firefox is at 5 % battery or less and not chargingFirefox refuses new locks and releases held ones. Plug in, then tap Retry.
  • The page is not on HTTPSThere is no navigator.wakeLock, so nothing is rejected. AwakeTab calls this unsupported and offers the video fallback after a tap.

Why a request fails, by engine

Checked against engine source on 26 September 2026.

CauseChromium (Chrome, Edge, Opera, Samsung Internet)WebKit (Safari)Gecko (Firefox)
Page hidden or tab not activeNotAllowedError; a held lock is releasedSameSame
Permissions-Policy or iframe without allowNotAllowedErrorNotAllowedErrorNotAllowedError
No recent user gestureNo checkNotAllowedError without a recent tap or an earlier gestureNo check
Battery at or below 5 %, unpluggedNo checkNo checkNotAllowedError; a held lock is released
Battery saver, Energy Saver or Low Power ModeNo checkNo checkNo check beyond the 5 % rule
Plain http pagenavigator.wakeLock is undefinedSameSame

Sources: Chromium wake_lock.cc, WebKit WakeLock.cpp and Firefox WakeLockJS.cpp.

The battery-saver row matters because many pages, including earlier versions of ours, blamed battery savers for NotAllowedError. The engines don't check for them. A saver can still change how long the device waits before the display goes dark, but that happens in the operating system's settings, outside the browser, and your promise never rejects because of it.

What happens when the tab is hidden?

Switching tabs or apps, minimising the window or locking the phone hides the document. A visible window that has lost focus keeps its lock. The sentinel fires release, and your page should show that honestly instead of a running timer. The same event fires when you call release() yourself and when the browser takes the lock back for its own reasons.

Good to knowThe browser releases the lock the moment the page is hidden, and it never asks again by itself. Listen for visibilitychange and request a new lock when the page is visible again.
resume.js
document.addEventListener('visibilitychange', () => {  if (wanted && !sentinel && document.visibilityState === 'visible') {    keepScreenOn();  }});

Safari usually accepts that second request thanks to the earlier tap; if it rejects, show a button so the next tap can ask.

What does the whole lifecycle look like?

Each box is one of AwakeTab's pill messages. The dashed line is the step most pages forget: asking again when the tab comes back.

Wake lock lifecycle

Ready moves to Starting when the page calls request. Starting moves to Screen awake when the browser grants the lock. Screen awake moves to Paused, tab hidden, when the page is hidden. When the page is visible again it requests a new lock and returns to Starting.

  1. Readyrequest()Starting…grantedScreen awake
  2. Screen awaketab hiddenPaused — tab hiddenvisible againStarting…
Other ways out
  1. Starting…NotAllowedErrorBlocked — here's the fix
  2. Readyno wakeLock in navigatorTap to use the fallbacktapAwake via video fallback
  3. Screen awakerelease()Ready
Labels are the exact pill copy. A timer runs only in "Screen awake" and "Awake via video fallback".

Can a page in an iframe ask for one?

Only if the parent page allows it. The screen-wake-lock feature is allowed for the page's own origin by default; a cross-origin iframe needs the embedding page to delegate it with allow="screen-wake-lock". Without it, the Permissions-Policy blocks the request and it fails with NotAllowedError, which is why "iframe without allow" is the first thing to check when a widget works on its own and fails when embedded.

embed.html
<iframe src="https://widget.example.com/timer" allow="screen-wake-lock"></iframe>

A site can also switch the feature off, or allow named origins, with a response header. screen-wake-lock=() blocks it everywhere on that page, including in the top-level document.

headers.txt
Permissions-Policy: screen-wake-lock=(self "https://widget.example.com")

How do I let go on purpose?

When the user stops or the time is up, release the lock and clear the flag that says they wanted it. Otherwise your visibilitychange handler will ask again the next time the tab is shown.

stop.js
async function letScreenSleep() {  wanted = false;  const s = sentinel;  sentinel = null;  await s?.release();} stopButton.addEventListener('click', letScreenSleep);

The video fallback trade-off

Before the API, libraries kept screens on by playing a tiny silent video in a loop. It still works as a fallback for browsers without the API, with three costs. It needs a user gesture, because browsers block video autoplay until someone interacts. It uses more power than a native lock, since the media pipeline stays busy. And your code can't easily tell whether it is working, only whether play() resolved. Use it only when wakeLock is missing, never as a second attempt after a NotAllowedError. NoSleep.js vs @awaketab/wake compares two libraries that handle this differently.

What can a wake lock not do?

It keeps the display on while the page is visible. It can't hold from a hidden tab, stop a laptop sleeping when the lid closes, or change an operating system rule such as a work sign-in lock. It sends no input, so it doesn't change a chat app's Away timer. On desktop Chrome or Edge, an extension with the power permission, such as AwakeTab for Chrome, can keep the screen on from a hidden tab; a web page can't.

Try it hereTry itStart a session, switch tabs, then come back and watch the pill. Ends at … if you start now.Start a 15-minute session

Questions

Not in Chromium: its screen-wake-lock permission is allowed by default, so a visible, top-level HTTPS page gets the lock without asking. Safari's condition is a recent tap on the page, and Firefox's is a battery above 5 % or a charger.

Soubhik Biswas · Builds AwakeTab. Sources checked Saturday, 26 September 2026. About AwakeTab