Chrome Side Panel API Complete Reference

8 min read

chrome.sidePanel API Reference

The chrome.sidePanel API provides a persistent side panel UI that displays alongside web content. Introduced in Chrome 114, it offers a more integrated experience than popups for extensions that need to display information while users browse.

Overview

Manifest Declaration

{
  "permissions": ["sidePanel"],
  "side_panel": {
    "default_path": "sidepanel.html"
  }
}

The side_panel manifest key defines the default panel page. The permission grants access to the chrome.sidePanel API.

API Methods

chrome.sidePanel.setOptions(details)

Configures the side panel for a specific tab or globally.

// Set global panel (same for all tabs)
await chrome.sidePanel.setOptions({ path: "sidepanel.html", enabled: true });

// Set panel for specific tab
await chrome.sidePanel.setOptions({
  tabId: 123,
  path: "page-specific-panel.html",
  enabled: true
});

// Disable panel for specific tab
await chrome.sidePanel.setOptions({ tabId: 123, enabled: false });

Parameters:

chrome.sidePanel.getOptions(details)

Retrieves the current side panel configuration.

// Get global options
const globalOptions = await chrome.sidePanel.getOptions({});

// Get per-tab options
const tabOptions = await chrome.sidePanel.getOptions({ tabId: 123 });

Parameters:

Returns: Promise resolving to an object with path and enabled properties.

chrome.sidePanel.open(details) — Chrome 116+

Opens the side panel programmatically. Requires a user gesture.

// Open panel in current tab
await chrome.sidePanel.open({});

// Open panel in specific tab
await chrome.sidePanel.open({ tabId: 123 });

// Open panel in specific window
await chrome.sidePanel.open({ windowId: 99 });

Parameters:

Note: This method requires a user gesture (e.g., action click, keyboard shortcut).

chrome.sidePanel.setPanelBehavior(details)

Configures behavior for opening the panel via toolbar icon clicks.

// Open panel when toolbar icon is clicked
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });

// Disable opening on icon click
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });

Parameters:

chrome.sidePanel.getPanelBehavior()

Retrieves the current panel behavior configuration.

const behavior = await chrome.sidePanel.getPanelBehavior();
console.log(behavior.openPanelOnActionClick);

Returns: Promise resolving to an object with openPanelOnActionClick boolean.

Per-Tab vs Global Configuration

Global Panel (Default)

Omit tabId to configure a global panel that appears the same for all tabs:

await chrome.sidePanel.setOptions({
  path: "global-panel.html",
  enabled: true
});

Per-Tab Panel

Provide tabId to configure different panels for different tabs:

// Different content for different sites
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === "complete" && tab.url) {
    const url = new URL(tab.url);
    if (url.hostname.includes("github.com")) {
      chrome.sidePanel.setOptions({
        tabId,
        path: "github-panel.html",
        enabled: true
      });
    } else if (url.hostname.includes("youtube.com")) {
      chrome.sidePanel.setOptions({
        tabId,
        path: "youtube-panel.html",
        enabled: true
      });
    }
  }
});

Precedence

Per-tab configuration overrides global configuration for that specific tab.

Communication

The side panel does not have dedicated events. Use standard message passing:

From Panel to Service Worker

// In side panel
chrome.runtime.sendMessage({ type: "GET_DATA", payload: { tabId: 123 } });

From Service Worker to Panel

// In service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "DATA_UPDATE") {
    // Send to panel
    chrome.tabs.sendMessage(message.tabId, message.data);
  }
});

Using @theluckystrike/webext-messaging

For type-safe messaging, use the @theluckystrike/webext-messaging package:

// Define message types
interface SidePanelMessages {
  GET_ANALYTICS: { url: string };
  ANALYTICS_DATA: { views: number; clicks: number };
}

The side panel has full chrome.* API access, similar to popup windows.

Differences from Popup

Feature Side Panel Popup
Persistence Stays open while browsing Closes on focus loss
Size Larger UI surface Limited by browser
Content updates Can update as user navigates Static until reopened
Multiple instances Single instance per window One per action click
Closing Must close explicitly Closes automatically

Code Examples

Basic Setup

manifest.json:

{
  "manifest_version": 3,
  "name": "My Side Panel Extension",
  "permissions": ["sidePanel"],
  "side_panel": {
    "default_path": "sidepanel.html"
  }
}

sidepanel.html:

<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 300px; padding: 16px; }
  </style>
</head>
<body>
  <h1>Side Panel</h1>
  <div id="content">Loading...</div>
  <script src="sidepanel.js"></script>
</body>
</html>

Open Panel on Toolbar Icon Click

// background.js
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });

This connects the side panel to the extension’s toolbar icon.

Two-Way Messaging with Service Worker

sidepanel.js:

// Send message to service worker
async function fetchData() {
  const tabId = chrome.runtime.id;
  const response = await chrome.runtime.sendMessage({
    type: "FETCH_ANALYTICS",
    tabId
  });
  document.getElementById("content").textContent = JSON.stringify(response);
}

// Listen for messages from service worker
chrome.runtime.onMessage.addListener((message) => {
  console.log("Received:", message);
});

background.js:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "FETCH_ANALYTICS") {
    // Process request and respond
    sendResponse({ data: "example" });
  }
});

Dynamic Content Based on Active Tab

// sidepanel.js - update content when tab changes
chrome.tabs.onActivated.addListener(async (activeInfo) => {
  const tab = await chrome.tabs.get(activeInfo.tabId);
  updatePanelContent(tab.url);
});

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === "complete" && tab.active) {
    updatePanelContent(tab.url);
  }
});

function updatePanelContent(url) {
  const urlObj = new URL(url);
  document.getElementById("domain").textContent = urlObj.hostname;
}

Cross-References

How do I open the side panel?

Use chrome.sidePanel.setOptions() to configure behavior, and users can open it via the extension icon in the toolbar.

Can the side panel open automatically?

Yes, use the “side_panel” permission with “openPanelOnActionClick”: true to open when users click your extension icon.


Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.