DevTools extensions add features to Chrome DevTools by accessing DevTools-specific
extension APIs through a DevTools page added to the extension.
![Architecture diagram showing DevTools page communicating with the
inspected window and the service worker. The service worker is shown
communicating with the content scripts and accessing extension APIs.
The DevTools page has access to the DevTools APIs, for example, creating panels.](https://developer.chrome.com/static/docs/extensions/how-to/devtools/extend-devtools/image/architecture-diagram.png) DevTools extension architecture.

The DevTools-specific extension APIs include the following:

- [`devtools.inspectedWindow`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow)
- [`devtools.network`](https://developer.chrome.com/docs/extensions/reference/devtools_network)
- [`devtools.panels`](https://developer.chrome.com/docs/extensions/reference/devtools_panels)
- [`devtools.recorder`](https://developer.chrome.com/docs/extensions/reference/devtools_recorder)
- [`devtools.performance`](https://developer.chrome.com/docs/extensions/reference/devtools_performance)

## The DevTools page

When a DevTools window opens, a DevTools extension creates an instance of its DevTools page that
exists as long as the window is open. This page has access to the DevTools APIs and extension APIs, and can do the following:

- Create and interact with panels using the [`devtools.panels`](https://developer.chrome.com/docs/extensions/reference/devtools_panels) APIs, including adding other extension pages as panels or sidebars to the DevTools window.
- Get information about the inspected window and evaluate code in the inspected window using the [`devtools.inspectedWindow`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow) APIs.
- Get information about network requests using the [`devtools.network`](https://developer.chrome.com/docs/extensions/reference/devtools_network) APIs.
- Extend the [Recorder panel](https://developer.chrome.com/docs/devtools/recorder.md.txt) using the [`devtools.recorder`](https://developer.chrome.com/docs/extensions/reference/devtools_recorder) APIs.
- Get information about the recording status of the [Performance panel](https://developer.chrome.com/docs/devtools/performance.md.txt) using the [`devtools.performance`](https://developer.chrome.com/docs/extensions/reference/devtools_performance) APIs.

The DevTools page can directly access extensions APIs. This includes being able
to communicate with the service worker using
[message passing](https://developer.chrome.com/docs/extensions/mv3/messaging).

## Create a DevTools extension

To create a DevTools page for your extension, add the `devtools_page` field in the extension
manifest:

```json
{
  "name": ...
  "version": "1.0",
  "devtools_page": "devtools.html",
  ...
}
```

The `devtools_page` field must point to an HTML page. Because the DevTools
page must be local to your extension, we recommend specifying it using a relative URL.

The members of the `browser.devtools` API are available only to the pages loaded within the DevTools
window while that window is open. Content scripts and other extension pages don't have access
to these APIs.

## The browser namespace and DevTools extensions

In Chrome versions 152 and later, extensions with a DevTools page can use the `browser` namespace.

In Chrome versions older than 152, the [`browser` namespace](https://developer.chrome.com/docs/extensions/develop/concepts/browser-namespace.md.txt)
was turned off for extensions that declare
`devtools_page`. The opt-out applied to the entire extension---not just the
DevTools page, but every script context where extension APIs run.

The reason was a compatibility gap with
[webextension-polyfill](https://github.com/mozilla/webextension-polyfill).
The `browser.devtools.*` APIs prior to Chrome 152 were callback-only---they didn't return
Promises natively---so DevTools extensions commonly relied on the polyfill to
wrap them. The polyfill skips wrapping whenever `browser` is defined,
assuming the host has already done the work. If Chrome had enabled `browser`
for these extensions, the polyfill would no-op and `browser.devtools.*`
calls would have stopped returning Promises. Keeping `browser` off kept the
polyfill keep wrapping.

The same opt-out also disabled the other Chrome 148 messaging API changes
for these extensions, including
[Promise responses in `runtime.onMessage`](https://developer.chrome.com/docs/extensions/develop/concepts/browser-namespace.md.txt#async-sendmessage).
The restriction had been lifted once the DevTools APIs supported Promises
natively.

## DevTools UI elements: panels and sidebar panes

In addition to the usual extension UI elements, such as browser actions, context menus and popups, a
DevTools extension can add UI elements to the DevTools window:

- A *panel* is a top-level tab, like the Elements, Sources, and Network panels.
- A *sidebar pane* presents supplementary UI related to a panel. The Styles, Computed Styles, and Event Listeners panes on the Elements panel are examples of sidebar panes. Depending on the version of Chrome you're using and where the DevTools window is docked, your sidebar panes might look like the following example image:

![DevTools window showing Elements panel and Styles sidebar pane.](https://developer.chrome.com/static/docs/extensions/how-to/devtools/extend-devtools/image/devtools-window-showing-e-9051f7f0347cd.png) DevTools window showing Elements panel and Styles sidebar pane.

Each panel is its own HTML file, which can include other resources (JavaScript, CSS, images, and so
on). To create a basic panel, use the following code:

```javascript
browser.devtools.panels.create("My Panel",
    "MyPanelIcon.png",
    "Panel.html",
    function(panel) {
      // code invoked on panel creation
    }
);
```

JavaScript executed in a panel or sidebar pane has access to the same APIs as the DevTools page.

To create a basic sidebar pane, use the following code:

```javascript
browser.devtools.panels.elements.createSidebarPane("My Sidebar",
    function(sidebar) {
        // sidebar initialization code here
        sidebar.setObject({ some_data: "Some data to show" });
});
```

There are several ways to display content in a sidebar pane:

- HTML content: Call [`setPage()`](https://developer.chrome.com/docs/extensions/reference/devtools_panels#method-ExtensionSidebarPane-setPage) to specify an HTML page to display in the pane.
- JSON data: Pass a JSON object to [`setObject()`](https://developer.chrome.com/docs/extensions/reference/devtools_panels#method-ExtensionSidebarPane-setObject).
- JavaScript expression: Pass an expression to [`setExpression()`](https://developer.chrome.com/docs/extensions/reference/devtools_panels#method-ExtensionSidebarPane-setExpression). DevTools evaluates the expression in the context of the inspected page, then displays the return value.

For both `setObject()` and `setExpression()`, the pane displays the value as it would appear in the
DevTools console. However, `setExpression()` lets you display DOM elements and arbitrary JavaScript
objects, while `setObject()` only supports JSON objects.

## Communicate between extension components

The following sections describe some helpful ways to allow DevTools extension components to
communicate with each other.

### Inject a content script

To inject a content script, use [`scripting.executeScript()`](https://developer.chrome.com/docs/extensions/reference/scripting#method-executeScript):

```javascript
// DevTools page -- devtools.js
browser.scripting.executeScript({
  target: {
    tabId: browser.devtools.inspectedWindow.tabId
  },
  files: ["content_script.js"]
});
```

You can retrieve the tab ID of the inspected window using the
[`inspectedWindow.tabId`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow#property-tabId) property.

If a content script has already been injected, you can use messaging APIs to
communicate with it.

### Evaluate JavaScript in the inspected window

You can use the [`inspectedWindow.eval()`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow#method-eval) method to execute JavaScript
code in the context of the inspected page. You can invoke the `eval()` method from a DevTools page,
panel, or sidebar pane.

> [!CAUTION]
> **Caution:** Use `eval()` only if you need access to the JavaScript content of an inspected page. Otherwise, we recommend using the [`scripting.executeScript()`](https://developer.chrome.com/docs/extensions/reference/api/scripting.md.txt#method-executeScript) method to run scripts. For more information, see [`inspectedWindow`](https://developer.chrome.com/docs/extensions/reference/api/devtools/inspectedWindow.md.txt).

By default, the expression is evaluated in the context of the main frame of the page.
`inspectedWindow.eval()` uses the same script execution context and options as code
entered in the DevTools console, which allows access to DevTools [Console Utilities
API](https://developer.chrome.com/docs/devtools/console/utilities) features when using `eval()`. For example, use it to inspect
the first script element within the `<head>` section of the HTML document:

```javascript
browser.devtools.inspectedWindow.eval(
  "inspect($$('head script')[0])",
  function(result, isException) { }
);
```

You can also set the `useContentScriptContext` to `true` when calling `inspectedWindow.eval()` to
evaluate the expression in the same context as the content scripts. To use this option, use a [static content script declaration](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts.md.txt#static-declarative) before calling `eval()`, either by calling `executeScript()` or by specifying a content
script in the `manifest.json` file. After the content script context loads, you can also use this option to
inject additional content scripts.

### Pass the selected element to a content script

The content script doesn't have direct access to the current selected element. However, any code you
execute using [`inspectedWindow.eval()`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow#method-eval) has access to the DevTools
console and Console Utilities APIs. For example, in evaluated code you can use `$0` to access the
selected element.

To pass the selected element to a content script:

1. Create a method in the content script that takes the selected element as an argument.

   ```javascript
   function setSelectedElement(el) {
       // do something with the selected element
   }
   ```
2. Call the method from the DevTools page using [`inspectedWindow.eval()`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow#method-eval)
   with the `useContentScriptContext: true` option.

   ```javascript
   browser.devtools.inspectedWindow.eval("setSelectedElement($0)",
       { useContentScriptContext: true });
   ```

The `useContentScriptContext: true` option specifies that the expression must be evaluated in the
same context as the content scripts, so it can access the `setSelectedElement` method.

### Get a reference panel's `window`

To call `postMessage()` from a devtools panel, you'll need a reference to its `window` object. Get a
panel's iframe window from the [`panel.onShown`](https://developer.chrome.com/docs/extensions/reference/devtools_panels#event-ExtensionPanel-onShown) event handler:

```javascript
extensionPanel.onShown.addListener(function (extPanelWindow) {
    extPanelWindow instanceof Window; // true
    extPanelWindow.postMessage( // ...
});
```

### Send messages from injected scripts to the DevTools page

Code injected directly into the page without a content script, including by appending a `<script>`
tag or calling [`inspectedWindow.eval()`](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow#method-eval), can't send messages to the
DevTools page using [`runtime.sendMessage()`](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage). Instead, we recommend
combining your injected script with a content script that can act as an intermediary, and using
the [`window.postMessage()`](https://developer.mozilla.org/docs/Web/API/Window.postMessage) method. The following example uses the background script
from the previous section:

```javascript
// injected-script.js

window.postMessage({
  greeting: 'hello there!',
  source: 'my-devtools-extension'
}, '*');
```

```javascript
// content-script.js

window.addEventListener('message', function(event) {
  // Only accept messages from the same frame
  if (event.source !== window) {
    return;
  }

  var message = event.data;

  // Only accept messages that we know are ours. Note that this is not foolproof
  // and the page can easily spoof messages if it wants to.
  if (typeof message !== 'object' || message === null ||
      message.source !== 'my-devtools-extension') {
    return;
  }

  browser.runtime.sendMessage(message);
});
```

Other alternative message-passing techniques can be found [on GitHub](https://github.com/GoogleChrome/devtools-docs/issues/143).

### Detect when DevTools opens and closes

To track whether the DevTools window is open, add an [onConnect](https://developer.chrome.com/docs/extensions/reference/runtime#event-onConnect) listener
to the service worker and call [connect()](https://developer.chrome.com/docs/extensions/reference/runtime#method-connect) from the DevTools page. Because
each tab can have its own DevTools window open, you might receive multiple connect events.
To track whether any DevTools window is open, count the connect and disconnect events as shown
in the following example:

```javascript
// background.js
var openCount = 0;
browser.runtime.onConnect.addListener(function (port) {
    if (port.name == "devtools-page") {
      if (openCount == 0) {
        alert("DevTools window opening.");
      }
      openCount++;

      port.onDisconnect.addListener(function(port) {
          openCount--;
          if (openCount == 0) {
            alert("Last DevTools window closing.");
          }
      });
    }
});
```

The DevTools page creates a connection like this:

```javascript
// devtools.js

// Create a connection to the service worker
const serviceWorkerConnection = browser.runtime.connect({
    name: "devtools-page"
});

// Send a periodic heartbeat to keep the port open.
setInterval(() => {
  port.postMessage("heartbeat");
}, 15000);
```

## DevTools extension examples

The examples on this page come from the following pages:

- [Polymer Devtools Extension](https://github.com/PolymerLabs/polymer-devtools-extension) - Uses many helpers running in the host page to query DOM/JS state to send back to the custom panel.
- [React DevTools Extension](https://github.com/facebook/react/tree/main/scripts/devtools) - Uses a submodule of the renderer to reuse DevTools UI components.
- [Ember Inspector](https://github.com/emberjs/ember-inspector) - Shared extension core with adapters for both Chrome and Firefox.
- [Coquette-inspect](https://github.com/thomasboyt/coquette-inspect) - A clean React-based extension with a debugging agent injected into the host page.
- [Sample Extensions](https://developer.chrome.com/docs/extensions/samples) have more worthwhile extensions to install, try out, and learn from.

## More information

For information on the standard APIs that extensions can use, see [browser.\* APIs](https://developer.chrome.com/docs/extensions/reference) and [web
APIs](https://developer.mozilla.org/docs/Web/API).

[Give us feedback!](https://groups.google.com/g/google-chrome-developer-tools) Your comments and suggestions help us improve the APIs.

## Examples

You can find examples that use DevTools APIs in [Samples](https://developer.chrome.com/docs/extensions/samples).