> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extensy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Workers

> Handle extension events, alarms, and cross-origin requests in the background service worker.

The background service worker is your extension's event hub. It wakes up when a registered event fires, handles it, then goes dormant again. Unlike the old MV2 background pages, service workers are non-persistent — they do not run continuously.

## How service workers differ from MV2 background pages

| | MV2 background page | MV3 service worker |
| - | - | - |
| Persistent | Yes — runs constantly | No — spins up on events, then sleeps |
| DOM access | Yes | No |
| Global state | Survives across events | Resets between wakes; use `chrome.storage` |
| Declared in manifest | `"background": { "scripts": [...] }` | `"background": { "service_worker": "background.js" }` |

> [!NOTE]
>
> Because the service worker can be terminated at any time, do not rely on in-memory variables to persist state between events. Write anything you need to keep to `chrome.storage`.

## Declare the service worker

In `manifest.json`:

```json theme={null}
{
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}
```

Use `"type": "module"` if you want to use ES module `import` syntax.

## A minimal background.js

```javascript theme={null}
// background.js

// Run once when the extension is installed or updated
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === "install") {
    chrome.storage.sync.set({ theme: "light", enabled: true });
    console.log("Extension installed. Default settings applied.");
  }
});

// Listen for messages from content scripts or the popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "PING") {
    sendResponse({ status: "alive" });
  }
  return true; // Keep the channel open for async responses
});
```

## Common events to listen for

> **runtime.onInstalled**
>
> Fires when the extension is first installed, updated, or Chrome is updated. Use this to set default storage values or open a welcome page.
>
> ```javascript theme={null}
> chrome.runtime.onInstalled.addListener(({ reason, previousVersion }) => {
>   if (reason === "update") {
>     console.log(`Updated from ${previousVersion}`);
>   }
> });
> ```

> **tabs.onUpdated**
>
> Fires when a tab's URL, title, or loading state changes. Requires the `"tabs"` permission.
>
> ```javascript theme={null}
> chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
>   if (changeInfo.status === "complete" && tab.url?.includes("github.com")) {
>     chrome.tabs.sendMessage(tabId, { type: "PAGE_LOADED" });
>   }
> });
> ```

> **alarms.onAlarm**
>
> Fires when a scheduled alarm triggers. Use this for periodic background tasks. Requires the `"alarms"` permission.
>
> ```javascript theme={null}
> // Schedule the alarm
> chrome.alarms.create("sync-data", { periodInMinutes: 30 });
>
> // Handle it
> chrome.alarms.onAlarm.addListener((alarm) => {
>   if (alarm.name === "sync-data") {
>     syncDataWithServer();
>   }
> });
> ```

> **runtime.onMessage**
>
> Fires when a content script, popup, or options page calls `chrome.runtime.sendMessage`. This is the primary way other extension contexts communicate with the background worker.
>
> ```javascript theme={null}
> chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
>   if (message.type === "FETCH_DATA") {
>     fetchFromApi(message.url).then(sendResponse);
>     return true; // Required for async sendResponse
>   }
> });
> ```

## Communicate between content scripts and the service worker

Send messages from a content script to the background worker:

```javascript theme={null}
// content.js
chrome.runtime.sendMessage({ type: "FETCH_DATA", url: "https://api.example.com/data" }, (response) => {
  console.log("Received:", response);
});
```

Send a message from the background worker to a specific tab's content script:

```javascript theme={null}
// background.js
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
  chrome.tabs.sendMessage(tabs[0].id, { type: "UPDATE_UI", payload: { count: 5 } });
});
```

> [!TIP]
>
> Always return `true` from `onMessage` listeners that call `sendResponse` asynchronously (e.g., inside a Promise or callback). Without it, the response channel closes before your async code runs.

## Make cross-origin API requests

The service worker is the right place for `fetch()` calls to external APIs that might be blocked by a page's Content Security Policy:

```javascript theme={null}
// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "CALL_API") {
    fetch(message.url, { headers: { Authorization: `Bearer ${message.token}` } })
      .then((res) => res.json())
      .then((data) => sendResponse({ data }))
      .catch((err) => sendResponse({ error: err.message }));
    return true;
  }
});
```

## Prompt Extensy to add service worker logic

Describe the event-driven behavior you want:

> "Every 15 minutes, fetch the latest headlines from `https://newsapi.org` and update the extension badge with the count of new articles."

> "When the user visits any page on `amazon.com`, send a message to the content script to highlight items that are on my wishlist."

> "On install, open the options page so the user can enter their API key."


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.