Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.

Starting from Firefox 53, no new legacy add-ons will be accepted on (AMO) for desktop Firefox and Firefox for Android.

Starting from Firefox 57, only extensions developed using WebExtensions APIs will be supported on Desktop Firefox and Firefox for Android.

Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to use WebExtensions APIs if they can. See the "Compatibility Milestones" document for more information.

A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.

This article documents the port object, which is used to communicate between a content script and the main add-on code.

The port object provides message sending and receiving API enabling conversations between a content script and the main add-on code.

Each end of the conversation has access to a port: the content script via the global self property, and the main add-on code via a worker object associated with the SDK module you've used to attach the content script, such as page-mod or page-worker.

For an overview of content scripts, see the main article.




The port.emit() function sends a message from one side to the other.

It may be called with any number of parameters, but is most likely to be called with a name for the message and an optional payload. The payload can be any value that is serializable to JSON.

From the content script to the main add-on code:

// content-script.js

var myMessagePayload = "some data";
self.port.emit("myMessage", myMessagePayload);

From the main add-on code to the content script:

// main.js

var myMessagePayload = "some data";
worker.port.emit("myMessage", myMessagePayload);


The port.on() function registers a function as a listener for a particular message sent from the other side using port.emit().

It takes two parameters: the name of the message and a function to handle it.

In a content script, to listen for "myMessage" sent from the main add-on code:

// content-script.js

self.port.on("myMessage", function handleMyMessage(myMessagePayload) {
  // Handle the message

In the main add-on code, to listen for "myMessage" sent from a a content script:

// main.js

worker.port.on("myMessage", function handleMyMessage(myMessagePayload) {
  // Handle the message


Often you'll want to receive a message just once, then stop listening. The port object offers a shortcut to do this: the once() method.

This example rewrites the content script in the port.removeListener() example so that it uses once():

// content-script.js

function getFirstParagraph() {
  var paras = document.getElementsByTagName('p');

self.port.once("get-first-para", getFirstParagraph);


You can use port.on() to listen for messages. To stop listening for a particular message, use port.removeListener(). This takes the same two parameters as port.on(): the name of the message, and the name of the listener function.

For example, here's an add-on that creates a page-worker and a button. The page-worker loads alongside a content script. The button sends the content script a message called "get-first-para" when it is clicked:

// main.js

pageWorker = require("sdk/page-worker").Page({
  contentScriptFile: require("sdk/self").data.url("listener.js"),
  contentURL: ""

  id: "get-first-para",
  label: "get-first-para",
  icon: "./icon-16.png",
  onClick: function() {
    console.log("sending 'get-first-para'");

The content script listens for "get-first-para". When it receives this message, the script logs the first paragraph of the document and then calls removeListener() to stop listening.

// content-script.js

function getFirstParagraph() {
  var paras = document.getElementsByTagName('p');
  self.port.removeListener("get-first-para", getFirstParagraph);

self.port.on("get-first-para", getFirstParagraph);

The result is that the paragraph is only logged the first time the button is clicked.

Due to bug 816272 the page-mod's removeListener() function does not prevent the listener from receiving messages that are already queued. This means that if "main.js" sends the message twice in successive lines, and the listener stops listening as soon as it receives the first message, then the listener will still receive the second message.

JSON-serializable values

The payload for a message can be any JSON-serializable value. When messages are sent their payloads are automatically serialized, and when messages are received their payloads are automatically deserialized, so you don't need to worry about serialization.

However, you do have to ensure that the payload can be serialized to JSON. This means that it needs to be a string, number, boolean, null, array of JSON-serializable values, or an object whose property values are themselves JSON-serializable. This means you can't send functions, and if the object contains methods they won't be encoded. You also can't include circular references.

For example, to include an array of strings in the payload:

// main.js

var pageMods = require("sdk/page-mod");
var self = require("sdk/self");

var pageMod = pageMods.PageMod({
  include: ['*'],
  onAttach: setupListener

function setupListener(worker) {
  worker.port.on('loaded', function(pageInfo) {

self.port.emit('loaded', [

Document Tags and Contributors

Contributors to this page: wbamberg, stoyanster, The_8472
Last updated by: wbamberg,