![Demián Renzulli](https://web.dev/images/authors/demianrenzulli.jpg) Demián Renzulli [X](https://twitter.com/drenzulli) [GitHub](https://github.com/demianrenzulli)

<br />

As a web developer, it's best practice to design your applications using the
lowest-trust security model possible, such as a Progressive Web App (PWA). This
approach maximizes your reach, minimizes the security overhead you have to
manage, and offers the greatest flexibility for both developers and users.
However, because the web is designed to be safe by default, its conservative
security model naturally restricts access to the operating system and certain
powerful device APIs.

Isolated Web Apps (IWAs) solve this by providing an isolated, bundled,
versioned, signed, and highly trusted application model built on top of the web
platform. Yet, before making the leap to an IWA, it's worth considering a more
gradual step: connecting your PWA with a Chrome Extension. Available in managed
ChromeOS environments---such as managed user sessions, Managed Guest Sessions
(MGS), or Kiosk mode---this technique lets your app use
[lower-level extension APIs](https://developer.chrome.com/docs/extensions/reference/api) through secure
message passing. The following diagram illustrates this progressive approach:
starting with a standard web application, adding the capabilities to become an
installable PWA, and finally exploring the PWA and Chrome Extension path to
unlock additional APIs.
![image](https://developer.chrome.com/static/docs/iwa/connect-to-extensions/image/fig1.png) The progressive enhancement path. By pairing an installable PWA with a companion Chrome Extension, developers can bridge the gap between the web's secure environment and lower-level OS and device features.

If your application requires advanced capabilities that remain unavailable even
with Chrome Extension APIs---such as
[Controlled Frame](https://developer.chrome.com/docs/iwa/controlled-frame) or the
[Direct Sockets API](https://developer.chrome.com/docs/iwa/direct-sockets)---then
migrating to an Isolated Web App (IWA) is your best path forward. However, while
IWAs unlock powerful new web features, you might still need specific
device-level APIs that are exclusive to Chrome Extensions, such as
`chrome.runtime.restart()` for rebooting a ChromeOS device in Kiosk mode.
Fortunately, you can connect an IWA to a Chrome Extension using the exact same
approach as a PWA. This technique is covered in the following steps.

## Step by step implementation

### Deploy the Companion Extension

Extensions are deployed through the Chrome Admin Console. Depending on your
target environment, you will configure this in the corresponding section (for
example, navigate to **Devices \> Chrome \> Apps \& Extensions \> Kiosks** for Kiosk
mode, or the respective tabs for **Users \& Browsers** or
**Managed Guest Sessions** ). You can either self-host the extension at a publicly accessible
link or host it directly in the Chrome Web Store. For more detailed instructions
on managing extensions refer to the
[official documentation](https://support.google.com/chrome/a/answer/2714278).

### Implement message passing

#### Extension setup

To receive and respond to messages from your web app, expose a background script
that listens for messages to arrive from the client (your web app) and then
proxy those requests to a corresponding API call. In the following example, a
request is proxied to restart the ChromeOS device when the web app sends a
custom message object that contains a `methodName` of `callRestart`.

**Background.js**

    // message handler - extension code
    chrome.runtime.onMessageExternal.addListener(function (request, sender, sendResponse) {
      if (request.methodName == 'callRestart') {
        chrome.runtime.restart();
      }
    });

The manifest for the extension can be configured to allow external function
calls to the extension using the `externally_connectable⁠⁠` key which specifies
what sites and extensions are allowed to call methods in the extension. More
information about Chrome extensions and manifest v3 can be found in the
[official documentation](https://developer.chrome.com/docs/extensions/develop/migrate)⁠⁠.

If you are connecting from a Progressive Web App (PWA), you will list the
standard HTTPS domain where your app is hosted under the matches array. Here is
an example of a manifest configured for a PWA running in Kiosk mode:

**Manifest.json**

    {
      "manifest_version": 3,
      "name": "Restart your kiosk app",
      "version": "1.0",
      "description": "This restarts your ChromeOS device.",
      "background": {
        "service_worker": "background.js"
      },
      "externally_connectable": {
        "accepts_tls_channel_id": false,
        "matches": [
          "*://developer.chrome.com/*"
        ]
      }
    }

If you are connecting from an Isolated Web App (IWA), the mechanism is
exactly the same, but the URL scheme changes. Because IWAs are securely packaged
and don't run on standard web servers, they use their own protocol. You must
add the IWA's origin using the `isolated-app://` scheme.

**Manifest.json**

    {
      "manifest_version": 3,
      "name": "IWA Companion Extension",
      "version": "1.1",
      "description": "Companion extension for the IWA",
      "background": {
        "service_worker": "/scripts/background.js"
      },
      "externally_connectable": {
        "matches": [
          "isolated-app://*/*"
        ]
      }
    }

> [!IMPORTANT]
> **Important:** While using a wildcard (`isolated-app://*/*`) is helpful for local testing and demos, it is highly recommended in a production environment to replace the wildcard with your IWA's specific Web Bundle ID (for example, `isolated-app://abcdefghijklmnopqrstuvwxyzabcdef/*`). This ensures your extension will strictly listen to messages coming from your app and no other installed IWAs.

This is the minimum amount of code required in an extension to listen for
messages from a PWA or IWA.

#### PWA and IWA setup

To call the extension from a web app, you need to know its static extension ID.
This ID can be found on the `chrome://extensions` page, shown when you install
your Chrome extension, or from the Chrome Web Store after the extension has been
uploaded. This lets your web app specify the exact extension to communicate
with. After that, call
[`chrome.runtime.sendMessage`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-sendMessage)⁠⁠
and pass in the extension ID with a message to send to the extension.

    const STATIC_EXTENSION_ID = 'abcdefghijklmnopqrstuvwxyz';
    // found from chrome extensions page of chrome web store.
    const callExtensionAPI = function (method) {
      chrome.runtime.sendMessage(STATIC_EXTENSION_ID, {
        methodName: method,
      });
    };
    callExtensionAPI('callRestart');

For more information on connecting web apps to extensions for message passing,
refer to [the Extensions documentation⁠⁠](https://developer.chrome.com/docs/extensions/develop/concepts/messaging#external-webpage).

> [!IMPORTANT]
> **Important:** Arbitrary communication between Extensions and web apps is prohibited. Establishing a link requires the web app to be allowed with the Extension's `externally_connectable` manifest entry and requires the app to know the Extension's static ID. Because the Extension cannot initiate the initial contact, a "handshake" technique is necessary. Use the IWA/PWA to establish the initial bridge and store the sender reference on the Extension side for future two-way communication.

## Demo

To see this implementation in action, explore the
[IWA Kitchen Sink repository](https://github.com/chromeos/iwa-sink).
This project serves as a
comprehensive playground for various IWA capabilities, featuring demos for
high-trust APIs like Direct Sockets and Controlled Frame.
It also provides a complete, working example of the IWA-to-Chrome-Extension
connection. The repository includes a sample companion extension and a dedicated
web interface that demonstrates how to use secure message passing to trigger
extension-exclusive methods. For example, you can test fetching the user's
profile information with the `chrome.identity.getProfileUserInfo()` API directly
from the Isolated Web App.

## Conclusion

Connecting your web applications to a Chrome Extension offers a secure,
progressive path to unlocking native-like device capabilities. As you design
your app's architecture, keep these key takeaways in mind:

1. **Start with the Web:** Default to a PWA for the best reach and lowest security overhead.
2. **Bridge the gap with Extensions:** For deeply integrated, OS-level features (like device rebooting in Kiosk mode), deploy a companion Chrome Extension and connect it to your application using secure message passing.
3. **Upgrade to IWAs only if needed:** Use Isolated Web Apps when you require high-trust APIs like Direct Sockets, Controlled Frame or any of the other IWA-only APIs.