Installation

MV2 MV3 Chrome Firefox Safari

Overview

@webext-core/messaging a simplified, type-safe wrapper around the web extension messaging APIs. It also provides a similar interface for communicating with web pages or injected scripts.

Don't like lower-level messaging APIs? Try out @webext-core/proxy-service for a more DX-friendly approach to executing code in the background script.

Installation

NPM
pnpm i @webext-core/messaging
import { defineExtensionMessaging } from '@webext-core/messaging';
CDN
curl -o messaging.js https://cdn.jsdelivr.net/npm/@webext-core/messaging/lib/index.global.js
<script src="/messaging.js"></script>
<script>
  const { defineExtensionMessaging } = webExtCoreMessaging;
</script>

Basic Usage

First, define a protocol map:

messaging.ts
interface ProtocolMap {
  getStringLength(data: string): number;
}

Then call defineExtensionMessaging, passing your ProtocolMap as the first type parameter.

Export the sendMessage and onMessage methods. These are what the rest of your extension will use to pass messages around.

messaging.ts
import { defineExtensionMessaging } from '@webext-core/messaging';

interface ProtocolMap {
  getStringLength(data: string): number;
}

export const { sendMessage, onMessage } = defineExtensionMessaging<ProtocolMap>();

Usually the onMessage function will be used in the background and messages will be sent from other parts of the extension.

background.ts
import { onMessage } from './messaging';

onMessage('getStringLength', message => {
  return message.data.length;
});
content-script.ts
import { sendMessage } from './messaging';

const length = await sendMessage('getStringLength', 'hello world');

console.log(length); // 11

Sending Messages to Tabs

You can also send messages from your background script to a tab, but you need to know the tabId.

content-script.ts
import { onMessage } from './messaging';

onMessage('getStringLength', message => {
  return message.data.length;
});
background.ts
import { sendMessage } from './messaging';

const length = await sendMessage('getStringLength', 'hello world', tabId);

Window Messaging

Inside a content script, you may need to communicate with a webpage or an injected script running in the page's JS context. In this case, you can use defineWindowMessenger or defineCustomEventMessenger, which use the window.postMessage and CustomEvent APIs respectively.

Window
import { defineWindowMessaging } from '@webext-core/messaging/page';

export interface WebsiteMessengerSchema {
  init(data: unknown): void;
  somethingHappened(data: unknown): void;
}

export const websiteMessenger = defineWindowMessaging<WebsiteMessengerSchema>({
  namespace: '<some-unique-string>',
});
Custom Event
import { defineCustomEventMessaging } from '@webext-core/messaging/page';

export interface WebsiteMessengerSchema {
  init(data: unknown): void;
  somethingHappened(data: unknown): void;
}

export const websiteMessenger = defineCustomEventMessaging<WebsiteMessengerSchema>({
  namespace: '<some-unique-string>',
});

Which one should I use?

Note the namespace option. Only messengers of the same type (window vs custom event) and same namespace will communicate. This prevents accidentially reacting to messages from the page or from another extension. Usually, it should be a unique string for your extension. The easiest method is to set it to browser.runtime.id, but if you're injecting a script, webextension-polyfill will not be available in the page context and you'll have to use something else or hardcode the extension's ID.

The messenger object can be used in the same way as the extension messenger, with sendMessage and onMessage.

Here, we're injecting a script, initializing it with data, and allowing the script to send data back to our content script.

Content Script
import { websiteMessenger } from './website-messenging';

const script = document.createElement('script');
script.src = browser.runtime.getUrl('/path/to/injected.js');
document.head.appendChild(script);

script.onload = () => {
  websiteMessenger.sendMessage("init", { ... });
}

websiteMessenger.onMessage("somethingHappened", (data) => {
  // React to messages from the injected script
});
Injected script
import { websiteMessenger } from './website-messenging';

websiteMessenger.onMessage('init', data => {
  // initialize injected script

  // eventually, send data back to the content script
  websiteMessenger.sendMessage("somethingHappened", { ... });
});