Chrome extension API support for Electron, built for Peersky.
Npm: @p2plabs/peersky-chrome-extensions.
Electron provides basic support for Chrome extensions out of the box. However, it only supports a subset of APIs with a focus on DevTools. Concepts like tabs, popups, and extension actions aren't known to Electron.
This library aims to bring extension support in Electron up to the level you'd come to expect from a browser like Google Chrome. API behavior is customizable so you can define how to handle things like tab or window creation specific to your application's needs.
npm install @p2plabs/peersky-chrome-extensions
| VeePN | Ghostery Reader |
|---|---|
![]() |
![]() |
Simple browser using Electron's default session and one tab.
const { app, BrowserWindow } = require('electron')
const { ElectronChromeExtensions } = require('@p2plabs/peersky-chrome-extensions')
app.whenReady().then(() => {
const extensions = new ElectronChromeExtensions({
license: 'GPL-3.0',
})
const browserWindow = new BrowserWindow()
// Adds the active tab of the browser
extensions.addTab(browserWindow.webContents, browserWindow)
browserWindow.loadURL('https://samuelmaddock.com')
browserWindow.show()
})Multi-tab browser with full support for Chrome extension APIs.
For a complete example, see the
electron-browser-shellproject.
const { app, session, BrowserWindow } = require('electron')
const { ElectronChromeExtensions } = require('@p2plabs/peersky-chrome-extensions')
app.whenReady().then(() => {
const browserSession = session.fromPartition('persist:custom')
const extensions = new ElectronChromeExtensions({
license: 'GPL-3.0',
session: browserSession,
createTab(details) {
// Optionally implemented for chrome.tabs.create support
},
selectTab(tab, browserWindow) {
// Optionally implemented for chrome.tabs.update support
},
removeTab(tab, browserWindow) {
// Optionally implemented for chrome.tabs.remove support
},
createWindow(details) {
// Optionally implemented for chrome.windows.create support
},
removeWindow(browserWindow) {
// Optionally implemented for chrome.windows.remove support
},
requestPermissions(extension, permissions) {
// Optionally implemented for chrome.permissions.request support
},
})
const browserWindow = new BrowserWindow({
webPreferences: {
// Use same session given to Extensions class
session: browserSession,
// Required for extension preload scripts
sandbox: true,
// Recommended for loading remote content
contextIsolation: true,
},
})
// Adds the active tab of the browser
extensions.addTab(browserWindow.webContents, browserWindow)
browserWindow.loadURL('https://samuelmaddock.com')
browserWindow.show()
})This module uses a preload script. When packaging your application, it's required that the preload script is included. This can be handled in two ways:
- Include
node_modulesin your packaged app. This allows@p2plabs/peersky-chrome-extensions/preloadto be resolved. - In the case of using JavaScript bundlers, you may need to copy the preload script next to your app's entry point script. You can try using copy-webpack-plugin, vite-plugin-static-copy, or rollup-plugin-copy depending on your app's configuration.
Here's an example for webpack configurations:
module.exports = {
entry: './index.js',
plugins: [
new CopyWebpackPlugin({
patterns: [require.resolve('@p2plabs/peersky-chrome-extensions/preload')],
}),
],
}Create main process handler for Chrome extension APIs.
optionsObjectlicenseString - Distribution license compatible with your application. See LICENSE.md for more details.
Valid options includeGPL-3.0,Patron-License-2020-11-19sessionElectron.Session (optional) - Session which should support Chrome extension APIs.session.defaultSessionis used by default.createTab(details) => Promise<[Electron.WebContents, Electron.BrowserWindow]>(optional) - Called whenchrome.tabs.createis invoked by an extension. Allows the application to handle how tabs are created.detailschrome.tabs.CreateProperties
selectTab(webContents, browserWindow)(optional) - Called whenchrome.tabs.updateis invoked by an extension with the option to set the active tab.webContentsElectron.WebContents - The tab to be activated.browserWindowElectron.BrowserWindow - The window which owns the tab.
removeTab(webContents, browserWindow)(optional) - Called whenchrome.tabs.removeis invoked by an extension.webContentsElectron.WebContents - The tab to be removed.browserWindowElectron.BrowserWindow - The window which owns the tab.
createWindow(details) => Promise<Electron.BrowserWindow>(optional) - Called whenchrome.windows.createis invoked by an extension.detailschrome.windows.CreateData
removeWindow(browserWindow) => Promise<Electron.BrowserWindow>(optional) - Called whenchrome.windows.removeis invoked by an extension.browserWindowElectron.BrowserWindow
assignTabDetails(details, webContents) => void(optional) - Called whenchrome.tabscreates an object for tab details to be sent to an extension background script. Provide this function to assign custom details such asdiscarded,frozen, orgroupId.detailschrome.tabs.TabwebContentsElectron.WebContents - The tab for which details are being created.
new ElectronChromeExtensions({
license: 'GPL-3.0',
createTab(details) {
const tab = myTabApi.createTab()
if (details.url) {
tab.webContents.loadURL(details.url)
}
return [tab.webContents, tab.browserWindow]
},
createWindow(details) {
const window = new BrowserWindow()
return window
},
})For a complete usage example, see the browser implementation in the
electron-browser-shell
project.
tabElectron.WebContents - A tab that the extension system should keep track of.windowElectron.BrowserWindow - The window which owns the tab.
Makes the tab accessible from the chrome.tabs API.
tabElectron.WebContents
Notify the extension system that a tab has been selected as the active tab.
tabElectron.WebContents - The tab from which the context-menu event originated.paramsElectron.ContextMenuParams - Parameters from thecontext-menuevent.
Returns Electron.MenuItem[] -
An array of all extension context menu items given the context.
Returns Object which maps special URL types to an extension URL. See chrome_urls_overrides for a list of
supported URL types.
Example:
{
newtab: 'chrome-extension://<id>/newtab.html'
}Returns:
popupPopupView - An instance of the popup.
Emitted when a popup is created by the chrome.browserAction API.
Returns:
urlOverridesObject - A map of url types to extension URLs.
Emitted after an extension is loaded with chrome_urls_overrides set.
The <browser-action-list> element provides a row of browser actions which may be pressed to activate the chrome.browserAction.onClicked event or display the extension popup.
To enable the element on a webpage, you must define a preload script which injects the API on specific pages.
partitionstring (optional) - TheElectron.Sessionpartition which extensions are loaded in. Defaults to the session in which<browser-action-list>lives.tabstring (optional) - The tab'sElectron.WebContentsID to use for displaying the relevant browser action state. Defaults to the active tab of the current browser window.alignmentstring (optional) - How the popup window should be aligned relative to the extension action. Defaults tobottom left. Use any assortment oftop,bottom,left, andright.
Inject the browserAction API to make the <browser-action-list> element accessible in your application.
import { injectBrowserAction } from '@p2plabs/peersky-chrome-extensions/browser-action'
// Inject <browser-action-list> element into our page
if (location.href === 'webui://browser-chrome.html') {
injectBrowserAction()
}The use of
importimplies that your preload script must be compiled using a JavaScript bundler like Webpack.
Add the <browser-action-list> element with attributes appropriate for your application.
<!-- Show actions for the same session and active tab of current window. -->
<browser-action-list></browser-action-list>
<!-- Show actions for custom session and active tab of current window. -->
<browser-action-list partition="persist:custom"></browser-action-list>
<!-- Show actions for custom session and a specific tab of current window. -->
<browser-action-list partition="persist:custom" tab="1"></browser-action-list>
<!-- If extensions are displayed in the bottom left of your browser UI, then
you can align the popup to the top right of the extension action. -->
<browser-action-list alignment="top right"></browser-action-list>For extension icons to appear in the list, the crx:// protocol needs to be handled in the Session
where it's intended to be displayed.
import { app, session } from 'electron'
import { ElectronChromeExtensions } from '@p2plabs/peersky-chrome-extensions'
app.whenReady().then(() => {
// Provide the session where your app will display <browser-action-list>
const appSession = session.defaultSession
ElectronChromeExtensions.handleCRXProtocol(appSession)
})The <browser-action-list> element is a Web Component. Its styles are encapsulated within a Shadow DOM. However, it's still possible to customize its appearance using the CSS shadow parts selector ::part(name).
Accessible parts include action and badge.
/* Layout action buttons vertically. */
browser-action-list {
flex-direction: column;
}
/* Modify size of action buttons. */
browser-action-list::part(action) {
width: 16px;
height: 16px;
}
/* Modify hover styles of action buttons. */
browser-action-list::part(action):hover {
background-color: red;
border-radius: 0;
}The following APIs are supported, in addition to those already built-in to Electron.
Click to reveal supported APIs
- chrome.action.setTitle
- chrome.action.getTitle
- chrome.action.setIcon
- chrome.action.setPopup
- chrome.action.getPopup
- chrome.action.setBadgeText
- chrome.action.getBadgeText
- chrome.action.setBadgeBackgroundColor
- chrome.action.getBadgeBackgroundColor
- chrome.action.enable
- chrome.action.disable
- chrome.action.openPopup
- chrome.action.onClicked
- chrome.commands.getAll
- chrome.commands.onCommand
- chrome.cookies.get
- chrome.cookies.getAll
- chrome.cookies.set
- chrome.cookies.remove
- chrome.cookies.getAllCookieStores
- chrome.cookies.onChanged
- chrome.contextMenus.create
- chrome.contextMenus.update
- chrome.contextMenus.remove
- chrome.contextMenus.removeAll
- chrome.contextMenus.onClicked
- chrome.notifications.clear
- chrome.notifications.create
- chrome.notifications.getAll
- chrome.notifications.getPermissionLevel
- chrome.notifications.update
- chrome.notifications.onButtonClicked
- chrome.notifications.onClicked
- chrome.notifications.onClosed
See Electron's Notification tutorial for how to support them in your app.
- chrome.runtime.connect
- chrome.runtime.getBackgroundPage
- chrome.runtime.getManifest
- chrome.runtime.getURL
- chrome.runtime.id
- chrome.runtime.lastError
- chrome.runtime.onConnect
- chrome.runtime.onInstalled
- chrome.runtime.onMessage
- chrome.runtime.onStartup
- chrome.runtime.onSuspend
- chrome.runtime.onSuspendCanceled
- chrome.runtime.openOptionsPage
- chrome.runtime.sendMessage
- chrome.storage.local
- chrome.storage.managed - fallback to
local - chrome.storage.sync - persisted under
extension-syncin the app userData directory (separate fromlocal; encrypted with ElectronsafeStoragewhen available)
- chrome.tabs.get
- chrome.tabs.getCurrent
- chrome.tabs.connect
- chrome.tabs.sendMessage
- chrome.tabs.create
- chrome.tabs.duplicate
- chrome.tabs.query
- chrome.tabs.highlight
- chrome.tabs.update
- chrome.tabs.move
- chrome.tabs.reload
- chrome.tabs.remove
- chrome.tabs.detectLanguage
- chrome.tabs.captureVisibleTab
- chrome.tabs.executeScript
- chrome.tabs.insertCSS
- chrome.tabs.setZoom
- chrome.tabs.getZoom
- chrome.tabs.setZoomSettings
- chrome.tabs.getZoomSettings
- chrome.tabs.discard
- chrome.tabs.goForward
- chrome.tabs.goBack
- chrome.tabs.onCreated
- chrome.tabs.onUpdated
- chrome.tabs.onMoved
- chrome.tabs.onActivated
- chrome.tabs.onHighlighted
- chrome.tabs.onDetached
- chrome.tabs.onAttached
- chrome.tabs.onRemoved
- chrome.tabs.onReplaced
- chrome.tabs.onZoomChange
[!NOTE] Electron does not provide tab functionality such as discarded, frozen, or group IDs. If an application developer wishes to implement this functionality, emit a
"tab-updated"event on the tab's WebContents forchrome.tabs.onUpdatedto be made aware of changes. Tab properties can be assigned using theassignTabDetailsoption provided to theElectronChromeExtensionsconstructor.
- chrome.webNavigation.getFrame (Electron 12+)
- chrome.webNavigation.getAllFrames (Electron 12+)
- chrome.webNavigation.onBeforeNavigate
- chrome.webNavigation.onCommitted
- chrome.webNavigation.onDOMContentLoaded
- chrome.webNavigation.onCompleted
- chrome.webNavigation.onErrorOccurred
- chrome.webNavigation.onCreateNavigationTarget
- chrome.webNavigation.onReferenceFragmentUpdated
- chrome.webNavigation.onTabReplaced
- chrome.webNavigation.onHistoryStateUpdated
- chrome.windows.get
- chrome.windows.getCurrent
- chrome.windows.getLastFocused
- chrome.windows.getAll
- chrome.windows.create
- chrome.windows.update
- chrome.windows.remove
- chrome.windows.onCreated
- chrome.windows.onRemoved
- chrome.windows.onFocusChanged
- chrome.windows.onBoundsChanged
These APIs are implemented in this package (beyond the checklist above). See the source under src/browser/api/ for details.
| API | Notes |
|---|---|
chrome.debugger |
Attach/detach and route CDP events (requires debugger permission). |
chrome.identity |
launchWebAuthFlow (OAuth via hidden BrowserWindow). getAuthToken is intentionally unsupported; use web-based OAuth. Initial options.url must be https, or http on loopback (localhost, 127.0.0.1, ::1) for local test servers. |
chrome.proxy |
Maps Chrome ProxyConfig to Electron session.setProxy. proxy.settings.clear only clears when the caller is the extension currently controlling proxy settings. |
chrome.declarativeNetRequest |
Rule evaluation for static/dynamic/session rules; integrates with the app’s webRequest bridge for blocking/redirects. |
chrome.webRequest |
Requires app-side hooks (notifyWebRequest* methods on ElectronChromeExtensions). |
chrome.scripting |
Script injection where supported by Electron. |
chrome.management |
Limited extension metadata helpers. |
chrome.permissions |
Runtime grants update an internal permission map; router permission checks honor optional permissions granted via chrome.permissions.request, not only manifest.permissions. |
From the peersky-chrome-extensions directory:
yarn install
yarn build
yarn testThe test suite runs Electron + Mocha (see spec/index.js). It expects a dev install that includes yargs (listed in devDependencies).
- CI-equivalent run (build + tests):
yarn test:ci - Native messaging tests (
spec/chrome-nativeMessaging-spec.ts) build a small SEA host underscript/native-messaging-host/. That step can be slow or flaky on some Windows/CI machines. In GitHub Actions we setSKIP_NATIVE_MESSAGING=1so the rest of the suite stays green; to run those tests locally, omit that variable and use a machine wherenode script/native-messaging-host/build.js <extensionId>completes successfully.
The workflow .github/workflows/peersky-chrome-extensions.yml runs yarn install --frozen-lockfile and yarn test:ci on Ubuntu (with xvfb-run) and Windows when files under peersky-chrome-extensions/ change.
- The latest version of Electron is recommended.
package.jsondeclarespeerDependencies.electronas>=35.0.0. - All background scripts are persistent.
- Usage of Electron's
webRequestAPI will preventchrome.webRequestlisteners from being called. - Chrome extensions are not supported in non-persistent/incognito sessions.
chrome.webNavigation.onDOMContentLoadedis only emitted for the top frame until support for iframes is added.- Service worker preload scripts require Electron's sandbox to be enabled. This is the default behavior, but might be overridden by the
--no-sandboxflag orsandbox: falsein thewebPreferencesof aBrowserWindow. Check for the--no-sandboxflag usingps -eaf | grep <appname>.
GPL-3
This package is published from the Peersky fork (github.com/p2plabsxyz/peersky-browser-shell); see LICENSE.md in the tarball.
For proprietary use, please contact me or sponsor me on GitHub under the appropriate tier to acquire a proprietary-use license. These contributions help make development and maintenance of this project more sustainable and show appreciation for the work thus far.


