# Window Management API

> window.getScreenDetails() lists attached screens so a PWA can place windows on a chosen one. ScreenDetailed members, window-management permission, fallbacks.

The Window Management API lets a page learn how many screens the device has and where each one sits, then place windows on a specific screen. `window.screen.isExtended` answers the first question without a prompt; `window.getScreenDetails()` asks for the `window-management` permission and resolves with a `ScreenDetails` object whose `screens` array holds one `ScreenDetailed` per display, with coordinates relative to the multi-screen origin, so `window.open()` features and `requestFullscreen({ screen })` can target a display.

Chrome 100 ships `getScreenDetails()`, `Screen.isExtended`, and `ScreenDetailed` on desktop; Edge and Opera inherit the implementation and Chrome for Android exposes the same interfaces. Firefox and Safari have no implementation in any version (BCD `api.Window.getScreenDetails`). Every interface is `[SecureContext]`.

## Syntax

```js
window.screen.isExtended

window.getScreenDetails()

screenDetails.screens
screenDetails.currentScreen

element.requestFullscreen({ screen: screenDetailed })
```

`isExtended` is a synchronous `boolean` on the existing `Screen` interface. `getScreenDetails()` returns `Promise<ScreenDetails>`; the same `ScreenDetails` object is returned on every call for a given `Window`, and it fires `screenschange` when a display is added or removed and `currentscreenchange` when the window moves to another display or its screen's properties change. `ScreenDetailed` inherits from `Screen`, so `width`, `height`, `availWidth`, `availHeight`, `colorDepth`, and `orientation` are available alongside the members below.

## Members

`getScreenDetails()` takes no arguments. The objects it returns carry the following members; the `screen` member of `FullscreenOptions` is the only input this API adds.

| Member | Type | Required | Description |
|---|---|---|---|
| `ScreenDetails.screens` | `FrozenArray<ScreenDetailed>` | Read-only | Every screen, sorted by `left` then `top`. A new array each time the set changes. |
| `ScreenDetails.currentScreen` | `ScreenDetailed` | Read-only | The screen the window is on; `===` to one entry of `screens`, so `screens.find(s => s !== currentScreen)` picks a secondary display. |
| `ScreenDetailed.left`, `ScreenDetailed.top` | `long` | Read-only | Distance from the multi-screen origin to the screen's left and top edge, in CSS pixels. |
| `ScreenDetailed.availLeft`, `ScreenDetailed.availTop` | `long` | Read-only | Same, for the available area that excludes taskbars and docks. Use these as `left`/`top` in `window.open()` features. |
| `ScreenDetailed.isPrimary` | `boolean` | Read-only | `true` for the display the OS designates as primary. |
| `ScreenDetailed.isInternal` | `boolean` | Read-only | `true` for a built-in panel such as a laptop display, `false` for an external monitor. |
| `ScreenDetailed.devicePixelRatio` | `float` | Read-only | Physical to CSS pixel ratio of that screen, which may differ from `window.devicePixelRatio`. |
| `ScreenDetailed.label` | `DOMString` | Read-only | OS-supplied name, for example `"Built-in Retina Display"` or `"DELL U2720Q"`; may be empty. |
| `FullscreenOptions.screen` | `ScreenDetailed` | No | Asks `requestFullscreen()` to move the window to that screen first. The browser may honour the user's preference instead. |

## Exceptions

The specification defines one rejection name for `getScreenDetails()` and reuses it for the related `minimize()`, `maximize()`, `restore()`, and `setResizable()` methods (see the [getScreenDetails() method](https://w3c.github.io/window-management/#api-window-getScreenDetails-method) (w3.org)).

| Exception | Condition |
|---|---|
| `NotAllowedError` | The document is not allowed to use the `window-management` Permissions Policy feature (the default allowlist is `'self'`); or the permission request resolved to `"denied"`, either because the user dismissed the prompt or because the call had no transient activation and the permission was still `"prompt"`. The window display-state methods also reject with it when the document is not an installed web app, lacks transient activation, or the OS refuses the change. |

`isExtended` throws nothing and returns `false` when Permissions Policy blocks the feature, so a `false` means "single screen or not allowed to know". A `screen` option naming a `ScreenDetailed` from another window's `ScreenDetails` is ignored rather than rejected.

:::observed
Calling `window.getScreenDetails()` in Chrome outside a user-activation handler, before the permission has been granted, rejects with `NotAllowedError: Transient activation is required to request permission.`; after the user chooses **Block** in the en-US prompt, or when the site setting is already blocked, the same call rejects with `NotAllowedError: Permission denied.` Both strings are in Chromium's [`window_screen_details.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/screen_details/window_screen_details.cc) (chromium.googlesource.com). The permission state can be read without a prompt through `navigator.permissions.query({ name: "window-management" })`.
:::

## Examples

Each example degrades to single-screen behaviour: Firefox and Safari do not reach the multi-screen branch, and on Chrome the branch is skipped when the user denies the permission.

### Opening a companion window on a secondary screen

Check `isExtended` first (no prompt), call `getScreenDetails()` inside the click handler so the permission prompt is allowed, and fall back to a plain `window.open()` on any failure.

```js
button.addEventListener("click", async () => {
  const url = "/presenter-notes";
  if (!("getScreenDetails" in window) || !window.screen.isExtended) {
    window.open(url, "_blank", "popup");
    return;
  }
  try {
    const details = await window.getScreenDetails();
    const target = details.screens.find((s) => s !== details.currentScreen) ?? details.currentScreen;
    const features = `popup,left=${target.availLeft},top=${target.availTop},width=${target.availWidth},height=${target.availHeight}`;
    window.open(url, "_blank", features);
  } catch (err) {
    console.warn(`${err.name}: ${err.message}`);
    window.open(url, "_blank", "popup");
  }
});
```

Because `left` and `top` in the features string are interpreted relative to the multi-screen origin once the permission is granted, negative coordinates are valid for a display placed left of or above the primary one.

### Presenting fullscreen on the external display while notes stay on the laptop

Pass the chosen `ScreenDetailed` as `FullscreenOptions.screen`. The spec lets a successful cross-screen fullscreen request waive the activation requirement for one immediately following `window.open()`, which is what makes the notes window possible from the same click.

```js
async function startPresentation(slides) {
  if (!("getScreenDetails" in window)) {
    return slides.requestFullscreen(); // current screen only
  }
  const details = await window.getScreenDetails();
  const external = details.screens.find((s) => !s.isInternal);
  if (!external) return slides.requestFullscreen();

  await slides.requestFullscreen({ screen: external });
  const notes = details.screens.find((s) => s.isInternal) ?? details.currentScreen;
  window.open("/notes", "_blank", `popup,left=${notes.availLeft},top=${notes.availTop},width=800,height=600`);
}
```

When every screen reports `isInternal: false` (a desktop with two monitors) the code falls back to the current screen rather than guessing.

### Reacting when a monitor is plugged in or removed

`screenschange` fires on the `ScreenDetails` object, not on `window`, and the `screens` array is replaced rather than mutated. Re-read it in the handler and close any companion window whose screen has gone.

```js
async function watchScreens(onChange) {
  if (!("getScreenDetails" in window)) {
    window.screen.addEventListener("change", () => onChange([window.screen]));
    return;
  }
  const details = await window.getScreenDetails();
  onChange(details.screens);
  details.addEventListener("screenschange", () => onChange(details.screens));
  details.addEventListener("currentscreenchange", () => {
    document.documentElement.style.setProperty("--dpr", details.currentScreen.devicePixelRatio);
  });
}
```

The single-screen fallback listens to the `change` event the same specification adds to `Screen`, fired when a basic property of the window's current screen (size, orientation, pixel ratio) changes.

## See also

- [display_override](/reference/manifest/display-override/), the manifest member that controls the window frame these screens show
- [Tabbed application mode and tab_strip](/reference/manifest/tabbed-display/)
- [PWAs on desktop](/reference/platforms/desktop/)
- [Window Management: getScreenDetails() method](https://w3c.github.io/window-management/#api-window-getScreenDetails-method) (w3.org)
- [Window Management: permission API integration](https://w3c.github.io/window-management/#permission-api-integration) (w3.org)
- [Manage several displays with the Window Management API](https://developer.chrome.com/docs/capabilities/web-apis/window-management) (developer.chrome.com)
- [Chromium: window_screen_details.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/screen_details/window_screen_details.cc) (chromium.googlesource.com)