Skip to content
Add to ChromeAdd to Edge Add to Home Screen

Docs

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 2026 7 min read By Soubhik Biswas
On this page
  1. Browser support
  2. Ask for a lock
  3. NotAllowedError
  4. By engine
  5. When the tab is hidden
  6. The lifecycle
  7. Inside an iframe
  8. Let go on purpose
  9. The video fallback
  10. Limits
  11. Honest limit
  12. Try it
  13. Questions
  14. Related

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 Edge 84 and later
  • Opera 70 and later
  • Samsung Internet 14 and later
  • Firefox 126 and later (May 2024)
  • Safari on Mac, iPhone and iPad 16.4 and later (March 2023)
  • iPhone and iPad Home Screen web apps iOS 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 active The 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 it Common in an iframe without allow="screen-wake-lock". AwakeTab shows "Blocked — here's the fix".
  • Safari has not had a tap yet Safari 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 charging Firefox refuses new locks and releases held ones. Plug in, then tap Retry.
  • The page is not on HTTPS There 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 know

The 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. request() granted tab hidden visible again Ready Starting… Screen awake Paused — tab hidden 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. request() granted tab hidden visible again Ready Starting… Screen awake Paused — tab hidden
Other ways out

Starting… NotAllowedError Blocked — here's the fix

Ready no wakeLock in navigator Tap to use the fallback tap Awake via video fallback

Screen awake release() 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

Start a session, switch tabs, then come back and watch the pill.

Ready Suggested · 15 min
Starts when you open this tab
15:00

ends at

How AwakeTab knows

    Session complete

    Keeps awake for
    15:00
    ends at
    Keeps awake for
    15:00
    ends at of 15 min
    15:00
    ends at
    Keeps awake for
    15:00
    ends at

    Stopped to save battery

    Keeps this screen on while this tab stays visible.

    Ends Until you stop

    Type any time. A time that has already passed today means tomorrow.

    ends at

    How long?

    ends at

    Share this session

    Waits for a tap to start.Starts keeping the screen awake as it opens.

    Is AwakeTab doing its job?

    Your rating

    Time's up. Keep going?

    Settings

    How AwakeTab behaves. Every change saves as you make it.

    Timer Ask to extendJust stop · · Notifies youNo notification
    When time is up
    End sound Chosen in Customize, with the focus sounds.
    Clock Follow language12-hour24-hour · Seconds shownNo seconds · English
    Focus Pomodoro lengths, breathing and your intention
    Device and privacy Battery stop onBattery stop off · Shortcuts onShortcuts off · Usage data onUsage data off
    15%
    AwakeTab stops the session at this level.Turn on the switch above to choose a level.
    Pro Your plan and the devices it covers. See what's in Pro Manage devices
    Reset to defaults

    Every setting here goes back to its default. Your notes, stats and Pro stay.

    Version 2026.10.21 · d0ae7ca

    Customize

    Make it yours. Every change shows on the clock at once.

    Every face shows the same time and state. Swipe the clock or press C to switch faces.

    Classic

    Retro

    Modern

    Your stats

    This week
    Today
    Streak
    All time

    time awake per day

    Pro keeps 12 weeks of history. See what's in Pro

    Keyboard shortcuts

    Shortcuts need a keyboard. Without one, the buttons on screen do the same things.

    Session

    Space
    Start or stop
    1 6
    Presets 15 min–4 h
    0
    Until I stop
    U
    Until a time
    Esc
    Close or stop

    Screen

    F
    Fullscreen
    C
    Next clock face (Shift+C goes back)
    D
    Cycle theme
    M
    Cycle ambient mode
    P
    Floating window

    Tools

    S
    Sounds
    N
    Notes
    T
    Focus timer
    B
    Breathing guide
    I
    What you're working on
    ?
    This overlay

    Press ? or Esc to close. Single-key shortcuts can be turned off in Settings.Single-key shortcuts can be turned off in Settings.

    AwakeTab Pro

    $12 a year

    • More themes, lamps and backgrounds
    • A sound mixer
    • More notes
    • A repeating Pomodoro
    • Message mode
    • 12 weeks of stats

    Keeping the screen awake stays free, with no ads on the awake screen.

    Clock

    Start a 15-minute session

    Questions

    Does the Screen Wake Lock API show a permission prompt?

    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.

    Why does the release event fire when my code never called release()?

    The browser releases the lock when the page becomes hidden, and Firefox also releases it when the battery drops to 5 % or less while unplugged. Check document.visibilityState in the handler: if the page is still visible, the browser took the lock back for another reason.

    Does a wake lock work on localhost?

    Yes. Browsers treat http://localhost as a secure context, so navigator.wakeLock exists there. A plain http:// address on your local network does not count, so test phones over HTTPS or through a tunnel.

    Can a service worker or web worker hold a wake lock?

    No. The WakeLock interface is exposed on the window only, and the lock is tied to a visible document. A worker can't ask for one, and a page can't keep one while it is in the background.

    Is it a problem to call request() while I already hold a lock?

    It works: each call returns a new sentinel, and the screen stays on while any of them is unreleased. Keep one reference and check sentinel.released before asking again, so stray sentinels don't keep the screen on after you meant to stop.

    Soubhik Biswas

    Builds AwakeTab. Sources checked Saturday, 26 September 2026. About AwakeTab