Become a GraphQL expert

We're launching a brand new course. Pre-sale is now live.

View course

Overstacked

Mon Jun 24 2024

Mastering the Chrome Messaging API

How to enable two-way communication between your extension and the web page it's running on.

cover image

Building chrome extensions becomes infinitely more powerful when you can communicate with the current web page. Interact with the DOM, localStorage or execute scripts of any kind. The Chrome Messaging API is the key to unlocking this power.

In this post, we'll take a look at how you can use the Chrome Messaging API to enable two-way communication between your extension and the web page it's running on.

What is the Chrome Messaging API?

When building an extension you'll have access to the chrome object. Within this object, you'll find the runtime property which contains methods like connect, sendMessage and onMessage.

Depending on your goals there are several ways to configure your messaging. If you need one or two-way communication or based on the frequency of messages you'll be sending.

Let's start with the simplest form and move on from there.

The basics

A chrome extension is split into three contexts. The extension UI, the background script and the content script. The content script is the script that runs on the web page itself.

Extension UI - This is the devtools or popup window that actually houses the extension UI (if your extension has a UI).

Background script - This handles events, long-running tasks, and serves as a message relay between different parts of the extension.

Content script - This script runs on the web page itself. You can interact with the DOM, localStorage or execute scripts of any kind.

The Chrome Messaging API allows you to send messages between these contexts. But to travel between the extension and the web page, it is usually necessary to go through the background script.

The manifest will need to register these files.

manifest.json:

{
  "name": "My extension",
  "version": "1.0",
  "description": "My devtools extension",
  "icons": {
    "128": "icon.png"
  },
  "manifest_version": 3,
  "devtools_page": "devtools.html",
  "permissions": [],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["contentScript.js"]
    }
  ],
  "background": {
    "service_worker": "background.js"
  }
}

One-way communication

If all you need to do is send data in one direction, either from the extension to the web page or vice versa, you can use the sendMessage method.

For example let's send a message from the extension to the content script. To do this we simply call chrome.runtime.sendMessage from the extension UI and use the background to relay this to the contentScript.

This serves as the simplest form of communication.

Extension UI (devtools.js or popup.js):

chrome.runtime.sendMessage({ data: 'Hello, Background!' })

Background Script (background.js):

// Listen for messages in the background script
chrome.runtime.onMessage.addListener((message, sender) => {
  // Forward the message to the content script
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, {
      data: message.data,
    })
  })
})

Content Script (contentScript.js):

// Listen for messages in the content script
chrome.runtime.onMessage.addListener((message, sender) => {
  console.log('Received message from Popup:', message.data)
  // Do something with the message
})

Two-way communication

If you need to relay messages back and forth between the extension and the web page, you can use the sendMessage method in combination with response callbacks.

Extension UI (devtools.js or popup.js):

// Send a message from the popup to the background script and handle the response
// through a callback function
chrome.runtime.sendMessage({ data: 'Hello, Background!' }, (response) => {
  console.log('Response from Content Script:', response.data)
})

Background Script (background.js):

// Listen for messages in the background script and use the same
// callback pattern to send a response back to the extension
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // Forward the message to the content script
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, { data: message.data }, (response) => {
      sendResponse(response)
    })
  })
})

Content Script (contentScript.js):

// Listen for messages in the content script and use the sendResponse
// function to send a response back to the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Received message from Popup:', message.data)
  sendResponse({ data: 'Hello, Popup!' })
})

High-frequency two-way communication

If you need to send messages back and forth between the extension and the web page at a high frequency, you can use the connect method to establish a long-lived connection. This reduces the overhead of creating new connections for each message.

Extension UI (devtools.js or popup.js):

// Create a connection to the background script
const port = chrome.runtime.connect({ name: 'popup-background-port' })

// Send a message through the port
port.postMessage({ data: 'Hello, Background!' })

// Listen for messages from the background script
port.onMessage.addListener((message) => {
  console.log('Response from Background:', message.data)
})

Background Script (background.js):

// Listen for connections from the popup or content script
chrome.runtime.onConnect.addListener((port) => {
  if (port.name === 'popup-background-port') {
    port.onMessage.addListener((message) => {
      // Forward the message to the content script
      chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        chrome.tabs.sendMessage(
          tabs[0].id,
          { data: message.data },
          (response) => {
            // Send the response back to the popup through the port
            port.postMessage({ data: response.data })
          }
        )
      })
    })
  }
})

Content Script (contentScript.js):

// Listen for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Received message from Popup:', message.data)
  // Prepare a response
  let responseData = 'Hello from Content Script!'
  sendResponse({ data: responseData })
})

Conclusion

Choose the right approach depending on your needs and the complexity of your extension. Ports are more efficient for high-frequency communication, while simple sendMessage calls are sufficient for one-off messages and offer a simpler API.


You can get more actionable ideas in my popular email newsletter. Each week, I share deep dives like this, plus the latest product updates. Join over 80,000 developers using my products and tools. Enter your email and don't miss an update.

You'll stay in the loop with my latest updates. Unsubscribe at any time.

© Copyright 2024 Overstacked. All rights reserved.

Created by Warren Day