How to Send a Request for Data from a Website to an Extension
Tags: programming, tutorial, webdev
Published: 2023-07-29 19:25:28

---
title: How to Send a Request for Data from a Website to an Extension
published: true
date: 2023-07-29 19:14:01 UTC
tags: ["programming", "tutorial", "webdev"]
canonical_url: https://www.thehappycactus.dev/posts/2023-07-29-message-webpage-from-extension/
---

While the [docs](https://developer.chrome.com/docs/extensions/mv3/) 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:

1. 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.
2. Your extension is using **Manifest V3**
3. Your extension saving data using the **Chrome Storage API** (`chrome.storage`). I’ll be using `chrome.storage.local` specifically, but I don’t think it should matter if you’re using a different storage area (`sync`, `session`, or `managed`).

Alrighty? Let’s get started!

* * *

# Quick Jumps

- **[Jump To the Extension Work ▼](#in-your-extension)**
- **[Jump To the Conclusion ▼](#in-conclusion)**

* * *

# 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:

1. Open your Extensions list in Chrome (`chrome://extensions/`)
2. Switch on the “Developer Mode” toggle (in the top-right corner)
3. 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](#1-add-a-background-script-to-your-extension)), 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](https://developer.chrome.com/docs/extensions/mv3/messaging/#external-webpage)!

```
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
    }
);
```

![Shia Lebouf in a wig and mustache mouthing the word 'magic'](https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif)

## 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

- **[Jump To the Website Work ▲](#in-your-website)**
- **[Jump To the Conclusion ▼](#in-conclusion)**

* * *

# 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:

1. Create the actual service worker file. E.g., `service-worker.js`
2. In your manifest, add the `background` key with your new background script file name under `service_worker`
3. 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](https://developer.chrome.com/docs/extensions/mv3/match_patterns/), 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](https://developer.chrome.com/docs/extensions/mv3/service_workers/) 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 website
- `sender` - a [`MessageSender`](https://developer.chrome.com/docs/extensions/reference/runtime/#type-MessageSender) 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](https://www.thehappycactus.dev/posts/2023-06-27-message-webpage-from-extension/#3-add-a-differentiating-type-to-your-message-optional), 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 initial `my-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!