How to Send a Request for Data from a Website to an Extension
While the docs for creating a Chrome extension are really quite good, I initially struggled to figure out the right way for my extension to communicate with a webapp (specifically: sending a request for data from the webapp to an extension and receiving a response). There can be a lot to read, and with extension changes from Manifest v2 to v3 some docs can be a bit misleading as well. I thought I’d distill my knowledge on this to a simple tutorial and hopefully make this easier for anybody out there who needs it.
If extension creation is new to you and you’re looking to request an extension’s saved data from your website, allow me to lay it out for you. 👍
Initial Assumptions/Requirements⌗
This tutorial assumes a couple things:
- You have at least a basic skeleton of both your website and extension complete; we won’t be going through how to create an extension (or website) from scratch.
- Your extension is using Manifest V3
- Your extension saving data using the Chrome Storage API (
chrome.storage
). I’ll be usingchrome.storage.local
specifically, but I don’t think it should matter if you’re using a different storage area (sync
,session
, ormanaged
).
Alrighty? Let’s get started!
Quick Jumps⌗
Website Setup - Sending a Message⌗
Let’s start by sending a message from your website to the extension.
1. Get your extension’s ID⌗
You’ll need to obtain the ID of your extension so your website knows the extension with which it’s communicating. This is easy enough:
- Open your Extensions list in Chrome (
chrome://extensions/
) - Switch on the “Developer Mode” toggle (in the top-right corner)
- The ID will be easily visible within your extension’s box
2. Send a message to the extension⌗
To send your request you’ll use chrome.runtime.sendMessage
within your webapp.
You may be saying to yourself, “Wait a second, I thought websites didn’t have access to certain Chrome extension-specific APIs! How can we be using chrome.runtime.sendMessage?” That’s a great question.
Within your extension setup (further below), you’ll update your manifest to include a new key called externally_connectable
- any site set up in this key’s value will have the messaging API exposed to it!
const extensionId = 'myextensionsid'; // You should have this from step 1
chrome.runtime.sendMessage(
extensionId,
'the message to send - can be a string or an object',
(response) => {
// Callback upon the extension's response
}
);
3. Add a differentiating type to your message (optional)⌗
When sending a message to your extension, you might want to include a type
or some such specific identifier in your data so the extension can verify that the message it’s receiving is the one you’ve intended for it. This is also useful if you have a couple different messages you want to be able to send; a type
will help you differentiate between the two.
const extensionId = 'myextensionsid'; // You should have this from step 1
chrome.runtime.sendMessage(
extensionId,
{
type: 'fetch-data',
data: 'some data you might want to send'
},
(response) => {
// Callback upon the extension's response
}
);
4. Handle the extension’s response⌗
We won’t implement anything complex here - though we’ll include a data
property on the returned response
.
const extensionId = 'myextensionsid'; // You should have this from step 1
chrome.runtime.sendMessage(
extensionId,
{
type: 'data-fetch',
data: 'some data you might want to send'
},
(response) => {
console.log(response.data);
}
);
Quick Jumps⌗
Extension Setup - Listening for and Responding to a Message⌗
With your website ready to send a message to the extension and receive a response, let’s ensure the extension is ready to listen and reply!
1. Add a service worker to your extension⌗
If you haven’t yet added a service worker yet, there’s multiple steps involved to set it up successfully:
- Create the actual service worker file. E.g.,
service-worker.js
- In your manifest, add the
background
key with your new background script file name underservice_worker
- In your manifest, add the
externally_connectable
key with an array of “matches” that include the websites for which you want this extension to listen
"background": {
"service_worker": "service-worker.js"
},
"externally_connectable": {
"matches": ["https://localhost:<portnumber>/*"]
}
A couple helpful notes here:
- Matches can be as complicated as you need them to be, but you can also easily focus them on a single page, including
localhost
for development. - Depending on if you’re looking at v2 or v3 Manifest versions in the docs, you may see the phrase “background script” used in a similar context to “service worker”. While I’m fairly sure there’s some overlap between these 2 concepts, for our purposes it’s important to work exclusively with the “extension service worker” concept, which the Chrome docs use explicitly for Manifest V3. This conflation tripped me up a few times, so I wanted to make that clear here. 👍
2. Add a message listener⌗
Add a listener to your service worker for the message incoming from your webapp:
chrome.runtime.onMessageExternal.addListener(
(message, sender, sendResponse) => {
// You'll add your response to the event here!
}
);
The only input parameter for addListener
is a callback whose signature includes:
message
- the message sent from your websitesender
- aMessageSender
object, particularly useful, I think, if your extension needs to know what tab is open.sendResponse
- callback that, as described, sends the response back to the caller (in our case, your web site)
3. Send a response back to your web site⌗
Once you’ve added the skeleton of your listener, it’s time to send a response. This response can be a simple string or a complex object.
If you added the optional type
key in your message sent from the webapp, check for it here to determine if you want to respond.
chrome.runtime.onMessageExternal.addListener(
async (request, sender, sendResponse) => {
if (!request.type === 'data-fetch') {
return;
}
const storageData = await chrome.storage.local.get('my-data');
sendResponse({
type: 'data-response',
data: storageData['my-data']
});
}
);
A couple items to note here:
- The listener function is now
async
, as we want to wait for the storage to return before sending the reponse. - Remember that when you call
storage.local.get()
, the object returned includes the initialmy-data
key:
await chrome.storage.local.set({
'my-data': {
firstName: 'Liz',
lastName: 'Lemon'
}
});
const storageData = await chrome.storage.local.get('my-data');
console.log(storageData);
/*
returns...
{
'my-data': {
firstName: 'Liz',
lastName: 'Lemon'
}
}
*/
I found this kind of counter-intuitive since you’re explicitly requesting my-data
; I would’ve expected it to just return the value. 🤷♀️ Happy to field info an opinions on this one!
In Conclusion⌗
That’s it! At this point you have both your website sending a request and console.log
in the response, and your extension listening, pulling the data from storage, and returning it. Hopefully someone finds this helpful!