Message passing

Because content scripts run in the context of a web page, not the extension that runs them,they often need ways to communicate with the rest of the extension. For example, an RSSreader extension might use content scripts to detect the presence of an RSS feed on a page,then notify the service worker to display an action icon for that page.

This communication uses message passing, which allows both extensions and content scripts tolisten for each other's messages and respond on the same channel. A message cancontain any valid JSON object (null, boolean, number, string, array, or object). There aretwo message passing APIs: one forone-time requests, and a more complex oneforlong-lived connections that allow multiple messages to be sent. For informationabout sending messages between extensions, see thecross-extension messages section.

One-time requests

To send a single message to another part of your extension, and optionally get aresponse, callruntime.sendMessage() ortabs.sendMessage().These methods let you send a one-time JSON-serializable message from a content script to theextension, or from the extension to a content script. To handle the response, use the returnedpromise. For backward compatibility with older extensions, you can instead pass a callback asthe last argument. You can't use a promise and a callback in the same call.

When you send a message, the event listener that handles the message is passed an optionalthird argument,sendResponse. This is a function that takes a JSON-serializable object thatis used as the return value to the function that sent the message. By default, thesendResponsecallback must be called synchronously. If you want to do asynchronous work to get the valuepassed tosendResponse, youmust return a literaltrue (not just a truthy value) from theevent listener. Doing so will keep the message channel open to the other end untilsendResponseis called.

// Event listenerfunctionhandleMessages(message,sender,sendResponse){fetch(message.url).then((response)=>sendResponse({statusCode:response.status}))// Since `fetch` is asynchronous, must send an explicit `true`returntrue;}// Message senderconst{statusCode}=awaitchrome.runtime.sendMessage({url:'https://example.com'});

For information on converting callbacks to promises and for using them in extensions, seethe Manifest V3 migration guide.

Sending a request from a content script looks like this:

content-script.js:

(async()=>{constresponse=awaitchrome.runtime.sendMessage({greeting:"hello"});// do something with response here, not outside the functionconsole.log(response);})();

If you want to respond synchronously to a message, just callsendResponse once you have the response, and returnfalse to indicate it's done. To respond asynchronously, returntrue to keep thesendResponse callback active until you are ready to use it. Async functions are not supported because they return a Promise, which is not supported.

To send a request to a content script, specify which tab the request applies toas shown in the following. This example works in service workers, popups, andchrome-extension:// pages opened as a tab.

(async()=>{const[tab]=awaitchrome.tabs.query({active:true,lastFocusedWindow:true});constresponse=awaitchrome.tabs.sendMessage(tab.id,{greeting:"hello"});// do something with response here, not outside the functionconsole.log(response);})();

To receive the message, set up aruntime.onMessage event listener. Theseuse the same code in both extensions and content scripts:

content-script.js or service-worker.js:

chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){console.log(sender.tab?"from a content script:"+sender.tab.url:"from the extension");if(request.greeting==="hello")sendResponse({farewell:"goodbye"});});

In the previous example,sendResponse() was called synchronously. To usesendResponse()asynchronously, addreturn true; to theonMessage event handler.

If multiple pages are listening foronMessage events, only the first to callsendResponse() fora particular event will succeed in sending the response. All other responses to that event will beignored.

Long-lived connections

To create a reusable long-lived message passing channel, callruntime.connect()to pass messages from a content script to an extension page, ortabs.connect()to pass messages from an extension page to a content script. You can name your channel todistinguish between different types of connections.

One potential use case for a long-lived connection is an automatic form-filling extension.The content script might open a channel to the extension page for a specific login, andsend a message to the extension for each input element on the page to request the formdata to fill in. The shared connection allows the extension to share state betweenextension components.

When establishing a connection, each end is assigned aruntime.Port object forsending and receiving messages through that connection.

Use the following code to open a channel from a content script, and send and listen for messages:

content-script.js:

varport=chrome.runtime.connect({name:"knockknock"});port.postMessage({joke:"Knock knock"});port.onMessage.addListener(function(msg){if(msg.question==="Who's there?")port.postMessage({answer:"Madame"});elseif(msg.question==="Madame who?")port.postMessage({answer:"Madame... Bovary"});});

To send a request from the extension to a content script, replace the call toruntime.connect()in the previous example withtabs.connect().

To handle incoming connections for either a content script or an extension page, setup aruntime.onConnect event listener. When another part of yourextension callsconnect(), it activates this event and theruntime.Portobject. The code for responding to incoming connections looks like this:

service-worker.js:

chrome.runtime.onConnect.addListener(function(port){console.assert(port.name==="knockknock");port.onMessage.addListener(function(msg){if(msg.joke==="Knock knock")port.postMessage({question:"Who's there?"});elseif(msg.answer==="Madame")port.postMessage({question:"Madame who?"});elseif(msg.answer==="Madame... Bovary")port.postMessage({question:"I don't get it."});});});

Port lifetime

Ports are designed as a two-way communication method between different parts of the extension. A top-level frame is the smallest part of an extension that can use a port.When part of an extension callstabs.connect(),runtime.connect() orruntime.connectNative(), it creates aPort that can immediatelysend messages usingpostMessage().

If there are multiple frames in a tab, callingtabs.connect() invokestheruntime.onConnect event once for each frame in the tab. Similarly, ifruntime.connect() is called, then theonConnect event can fire once for everyframe in the extension process.

You might want to find out when a connection is closed, for example if you're maintaining separatestates for each open port. To do this, listen to theruntime.Port.onDisconnect event. Thisevent fires when there are no valid ports at the other end of the channel, which can have any of the following causes:

  • There are no listeners forruntime.onConnect at the other end.
  • The tab containing the port is unloaded (for example, if the tab is navigated).
  • The frame whereconnect() was called has unloaded.
  • All frames that received the port (viaruntime.onConnect) have unloaded.
  • runtime.Port.disconnect() is called bythe other end. If aconnect() call results in multiple ports at the receiver's end, anddisconnect() is calledon any of these ports, then theonDisconnect event only fires at the sending port, not at theother ports.

Cross-extension messaging

In addition to sending messages between different components in your extension, you can use themessaging API to communicate with other extensions. This lets you expose a public API for otherextensions to use.

To listen for incoming requests and connections from other extensions, use theruntime.onMessageExternalorruntime.onConnectExternal methods. Here's an example ofeach:

service-worker.js

// For a single request:chrome.runtime.onMessageExternal.addListener(function(request,sender,sendResponse){if(sender.id===blocklistedExtension)return;// don't allow this extension accesselseif(request.getTargetData)sendResponse({targetData:targetData});elseif(request.activateLasers){varsuccess=activateLasers();sendResponse({activateLasers:success});}});// For long-lived connections:chrome.runtime.onConnectExternal.addListener(function(port){port.onMessage.addListener(function(msg){// See other examples for sample onMessage handlers.});});

To send a message to another extension, pass the ID of the extension you want to communicate with as follows:

service-worker.js

// The ID of the extension we want to talk to.varlaserExtensionId="abcdefghijklmnoabcdefhijklmnoabc";// For a minimal request:chrome.runtime.sendMessage(laserExtensionId,{getTargetData:true},function(response){if(targetInRange(response.targetData))chrome.runtime.sendMessage(laserExtensionId,{activateLasers:true});});// For a long-lived connection:varport=chrome.runtime.connect(laserExtensionId);port.postMessage(...);

Send messages from web pages

Extensions can also receive and respond to messages from other web pages, but can't send messagesto web pages. To send messages from a web page to an extension,specify in yourmanifest.json which websites you want to communicate with usingthe"externally_connectable" manifest key. For example:

manifest.json

"externally_connectable":{"matches":["https://*.example.com/*"]}

This exposes the messaging API to any page that matches the URL patterns you specify. The URLpattern must contain at least asecond-level domain; that is, hostname patterns such as "*","*.com", "*.co.uk", and "*.appspot.com" are not supported. Starting in Chrome 107, you can use<all_urls> to access all domains. Note that because it affects all hosts,Chrome web store reviews for extensions that use itmay take longer.

Use theruntime.sendMessage() orruntime.connect() APIs to senda message to a specific app or extension. For example:

webpage.js

// The ID of the extension we want to talk to.consteditorExtensionId='abcdefghijklmnoabcdefhijklmnoabc';// Check if extension is installedif(chrome &&chrome.runtime){// Make a request:chrome.runtime.sendMessage(editorExtensionId,{openUrlInEditor:url},(response)=>{if(!response.success)handleError(url);});}

From your extension, listen to messages from web pages using theruntime.onMessageExternal orruntime.onConnectExternalAPIs as incross-extension messaging. Here's an example:

service-worker.js

chrome.runtime.onMessageExternal.addListener(function(request,sender,sendResponse){if(sender.url===blocklistedWebsite)return;// don't allow this web page accessif(request.openUrlInEditor)openUrl(request.openUrlInEditor);});

Native messaging

Extensionscan exchange messages with native applications that are registered as anative messaging host. To learn more about this feature, seeNative messaging.

Security considerations

Here are a few security considerations related to messaging.

Content scripts are less trustworthy

Content scripts are less trustworthy than the extension service worker.For example, a malicious web page might be able to compromise the rendering process thatruns the content scripts. Assume that messages from a content script might have beencrafted by an attacker and make sure tovalidate and sanitize all input.Assume any data sent to the content script might leak to the web page.Limit the scope of privileged actions that can be triggered by messages received from contentscripts.

Cross-site scripting

Make sure to protect your scripts againstcross-site scripting. When receiving data from an untrusted source such as user input, other websites through a content script, or an API, take care to avoid interpreting this as HTML or using it in a way which could allow unexpected code to run.

Safer methods

Use APIs that don't run scripts whenever possible:

service-worker.js

chrome.tabs.sendMessage(tab.id,{greeting:"hello"},function(response){// JSON.parse doesn't evaluate the attacker's scripts.varresp=JSON.parse(response.farewell);});

service-worker.js

chrome.tabs.sendMessage(tab.id,{greeting:"hello"},function(response){// innerText does not let the attacker inject HTML elements.document.getElementById("resp").innerText=response.farewell;});
Unsafe methods

Avoid using the following methods that make your extension vulnerable:

service-worker.js

chrome.tabs.sendMessage(tab.id,{greeting:"hello"},function(response){// WARNING! Might be evaluating a malicious script!varresp=eval(`(${response.farewell})`);});

service-worker.js

chrome.tabs.sendMessage(tab.id,{greeting:"hello"},function(response){// WARNING! Might be injecting a malicious script!document.getElementById("resp").innerHTML=response.farewell;});

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2012-09-18 UTC.