# The Clients API: a service worker's access to client objects

> How the Clients interface, exposed to a service worker as self.clients, provides get(), matchAll(), openWindow(), and claim() to find, focus, message, or take control of window and worker clients, and its Baseline widely-available browser support.

**In one line:** The `Clients` interface, accessed as `self.clients` inside a service worker, provides access to `Client` objects (windows, workers) matching the query options passed in — with `matchAll({ includeUncontrolled: true })`, this can include clients that share the same storage key/origin but are not controlled by, or associated with the registration of, this service worker.

This feature is only available in service workers.

## Accessing it

```js
// Inside a service worker
self.clients;
```

## Methods

| Method | Returns | Purpose |
|---|---|---|
| `Clients.get(id)` | `Promise<Client \| undefined>` | Returns a `Client` matching a given `id`, or `undefined` if no client matches. |
| `Clients.matchAll(options)` | `Promise<Array<Client>>` | Returns an array of `Client` objects. By default only controlled clients are returned; an options argument allows control over the types of clients returned, including uncontrolled ones. |
| `Clients.openWindow(url)` | `Promise<WindowClient \| null>` | Opens a new browser window for a given URL, resolving to `null` if the platform cannot return a same-origin `WindowClient` for it. |
| `Clients.claim()` | `Promise<void>` | Allows an active service worker to set itself as the controller for all clients within its scope. |

## Example

Finding an already-open chat window, focusing it, or opening a new one, then messaging it:

```js
addEventListener("notificationclick", (event) => {
  event.waitUntil(
    (async () => {
      const allClients = await clients.matchAll({
        includeUncontrolled: true,
      });

      let chatClient;

      // Let's see if we already have a chat window open:
      for (const client of allClients) {
        const url = new URL(client.url);

        if (url.pathname === "/chat/") {
          // Excellent, let's use it!
          client.focus();
          chatClient = client;
          break;
        }
      }

      // If we didn't find an existing chat window,
      // open a new one:
      chatClient ??= await clients.openWindow("/chat/");

      // Message the client:
      chatClient.postMessage("New chat messages!");
    })(),
  );
});
```

## Browser support

The `Clients` interface is Baseline widely available: it is well established and works across many devices and browser versions, and has been available across browsers since April 2018.

## Practical checklist

- [ ] Use `clients.matchAll()` with `includeUncontrolled` when a service worker needs to find windows it does not yet control — by default `matchAll()` only returns controlled clients.
- [ ] Handle `clients.get(id)` resolving to `undefined` and `clients.openWindow(url)` resolving to `null` when no matching client can be returned.
- [ ] Call `clients.claim()` in the service worker's `activate` handler when it should take control of existing clients immediately, rather than waiting for the next navigation.
- [ ] Use `clients.openWindow()` only in response to a user gesture such as a notification click, matching the pattern shown above.

## Cross-references

- [Navigation preload](/reference/service-worker/navigation-preload/) — another service worker capability exposed via a similarly scoped API